Compare commits

..

21 Commits

Author SHA1 Message Date
Gregory Schier
0b8a18edae Safe URI for reflection 2024-06-20 13:11:40 -07:00
Gregory Schier
0bb3f14b4c Fix button styles 2024-06-20 12:55:13 -07:00
Gregory Schier
c18d30b89f Refactor gRPC reflection! 2024-06-20 12:49:58 -07:00
Gregory Schier
8f06a834c8 Update script 2024-06-20 09:23:50 -07:00
Gregory Schier
bd948f9cd6 Actually write the file 2024-06-20 09:20:04 -07:00
Gregory Schier
d70254bcdc Generic build version 2024-06-20 09:17:28 -07:00
Gregory Schier
fac5385d5d Bump version 2024-06-20 09:07:38 -07:00
Gregory Schier
e8bcc695bb Better fallback request name generation 2024-06-20 09:03:24 -07:00
Gregory Schier
88aeb0e530 Fix GRPC with files not refreshing, and tight render loop 2024-06-20 08:40:10 -07:00
Gregory Schier
92b1582232 Better gRPC reflection errors 2024-06-18 10:54:39 -07:00
Gregory Schier
196990c077 Bump version 2024-06-18 10:41:22 -07:00
Gregory Schier
c42b8cf538 Editor autocomplete same font size as editor 2024-06-18 09:43:51 -07:00
Gregory Schier
085274c960 Move GraphQL introspection out of LocalStorage 2024-06-18 09:43:28 -07:00
Gregory Schier
7ef187abf6 REvert other thing 2024-06-17 17:05:51 -07:00
Gregory Schier
12eac34d95 Fix editor tags wrapping behavior 2024-06-17 17:05:29 -07:00
Gregory Schier
c29b3c6509 Change version 2024-06-17 15:09:44 -07:00
Gregory Schier
7faa423aba Recursive environments 2024-06-17 12:24:06 -07:00
Gregory Schier
5b2162e48d Fix flash loading response viewer 2024-06-17 11:43:45 -07:00
Gregory Schier
ee776143b2 Fix parsing notification timestamp 2024-06-17 11:43:20 -07:00
Gregory Schier
7a18fb29e4 More dynamic plugin access 2024-06-15 22:13:01 -07:00
Gregory Schier
4485cad9e8 Workspace dropdown to RadioDropdown 2024-06-14 17:07:35 -07:00
96 changed files with 857 additions and 548 deletions

View File

@@ -12,7 +12,7 @@ module.exports = {
parserOptions: {
project: ["./tsconfig.json"]
},
ignorePatterns: ["src-tauri/**/*", "plugins/**/*"],
ignorePatterns: ["scripts/**/*", "src-tauri/**/*", "plugins/**/*"],
settings: {
react: {
version: "detect"

View File

@@ -54,6 +54,10 @@ jobs:
run: npm run lint
- name: Run tests
run: npm test
- name: Set version
run: npm run replace-version
env:
YAAK_VERSION: ${{ github.ref_name }}
- uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

34
package-lock.json generated
View File

@@ -1,11 +1,11 @@
{
"name": "yaak-app",
"name": "yaak",
"version": "0.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "yaak-app",
"name": "yaak",
"version": "0.0.0",
"dependencies": {
"@codemirror/commands": "^6.2.1",
@@ -497,9 +497,9 @@
"integrity": "sha512-myLfHbVOqaq9UtUKqR/nZA/OY7xFjQMdfgfqeZIBK4d0hA6pgxArvdv8M+6NUzzBsjWLOtvApv8YHr4qM+Kpfg=="
},
"node_modules/@codemirror/autocomplete": {
"version": "6.16.0",
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.16.0.tgz",
"integrity": "sha512-P/LeCTtZHRTCU4xQsa89vSKWecYv1ZqwzOd5topheGRf+qtacFgBeIMQi3eL8Kt/BUNvxUWkx+5qP2jlGoARrg==",
"version": "6.16.3",
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.16.3.tgz",
"integrity": "sha512-Vl/tIeRVVUCRDuOG48lttBasNQu8usGgXQawBXI7WJAiUDSFOfzflmEsZFZo48mAvAaa4FZ/4/yLLxFtdJaKYA==",
"dependencies": {
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.0.0",
@@ -561,9 +561,9 @@
}
},
"node_modules/@codemirror/language": {
"version": "6.10.1",
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.1.tgz",
"integrity": "sha512-5GrXzrhq6k+gL5fjkAwt90nYDmjlzTIJV8THnxNFtNKWotMIlzzN+CpqxqwXOECnUdOndmSeWntVrVcv5axWRQ==",
"version": "6.10.2",
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.2.tgz",
"integrity": "sha512-kgbTYTo0Au6dCSc/TFy7fK3fpJmgHDv1sG1KNQKJXVi+xBTEeBPY/M30YXiU6mMXeH+YIDLsbrT4ZwNRdtF+SA==",
"dependencies": {
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.23.0",
@@ -574,9 +574,9 @@
}
},
"node_modules/@codemirror/lint": {
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.7.0.tgz",
"integrity": "sha512-LTLOL2nT41ADNSCCCCw8Q/UmdAFzB23OUYSjsHTdsVaH0XEo+orhuqbDNWzrzodm14w6FOxqxpmy4LF8Lixqjw==",
"version": "6.8.1",
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.1.tgz",
"integrity": "sha512-IZ0Y7S4/bpaunwggW2jYqwLuHj0QtESf5xcROewY6+lDNwZ/NzvR4t+vpYgg9m7V8UXLPYqG+lu3DF470E5Oxg==",
"dependencies": {
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0",
@@ -599,9 +599,9 @@
"integrity": "sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A=="
},
"node_modules/@codemirror/view": {
"version": "6.26.3",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.26.3.tgz",
"integrity": "sha512-gmqxkPALZjkgSxIeeweY/wGQXBfwTUaLs8h7OKtSwfbj9Ct3L11lD+u1sS7XHppxFQoMDiMDp07P9f3I2jWOHw==",
"version": "6.28.1",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.28.1.tgz",
"integrity": "sha512-BUWr+zCJpMkA/u69HlJmR+YkV4yPpM81HeMkOMZuwFa8iM5uJdEPKAs1icIRZKkKmy0Ub1x9/G3PQLTXdpBxrQ==",
"dependencies": {
"@codemirror/state": "^6.4.0",
"style-mod": "^4.1.0",
@@ -7369,9 +7369,9 @@
"integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="
},
"node_modules/json-schema-library": {
"version": "9.3.4",
"resolved": "https://registry.npmjs.org/json-schema-library/-/json-schema-library-9.3.4.tgz",
"integrity": "sha512-220lm9RVt9BUeF2QhBT711aX4IogUHhPT8Tjhkksc4CUw8WmChFMuf0mJdpDAHDfJDkI064jcZIH8P70HdPAOA==",
"version": "9.3.5",
"resolved": "https://registry.npmjs.org/json-schema-library/-/json-schema-library-9.3.5.tgz",
"integrity": "sha512-5eBDx7cbfs+RjylsVO+N36b0GOPtv78rfqgf2uON+uaHUIC62h63Y8pkV2ovKbaL4ZpQcHp21968x5nx/dFwqQ==",
"dependencies": {
"@sagold/json-pointer": "^5.1.2",
"@sagold/json-query": "^6.1.3",

View File

@@ -25,7 +25,8 @@
"build:plugin:filter-xpath": "cd plugins/filter-xpath && vite build --emptyOutDir",
"test": "vitest",
"coverage": "vitest run --coverage",
"prepare": "husky install"
"prepare": "husky install",
"replace-version": "node ./scripts/replace-version.cjs"
},
"dependencies": {
"@codemirror/commands": "^6.2.1",

View File

@@ -0,0 +1,15 @@
const path = require('path');
const fs = require('fs');
const version = process.env.YAAK_VERSION?.replace('v', '');
if (!version) {
throw new Error('YAAK_VERSION environment variable not set')
}
const tauriConfigPath = path.join(__dirname, '../src-tauri/tauri.conf.json');
const tauriConfig = JSON.parse(fs.readFileSync(tauriConfigPath, 'utf8'));
tauriConfig.version = version;
console.log('Writing version ' + version + ' to ' + tauriConfigPath)
fs.writeFileSync(tauriConfigPath, JSON.stringify(tauriConfig, null, 2));

8
src-tauri/Cargo.lock generated
View File

@@ -2290,6 +2290,7 @@ dependencies = [
"hyper 0.14.29",
"hyper-rustls 0.24.2",
"log",
"md5",
"prost",
"prost-reflect",
"prost-types",
@@ -3250,6 +3251,12 @@ dependencies = [
"digest",
]
[[package]]
name = "md5"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
[[package]]
name = "memchr"
version = "2.7.2"
@@ -8172,6 +8179,7 @@ dependencies = [
"tauri-plugin-updater",
"tauri-plugin-window-state",
"templates",
"thiserror",
"tokio",
"tokio-stream",
"uuid",

View File

@@ -56,3 +56,4 @@ tauri-plugin-window-state = "2.0.0-beta"
tokio = { version = "1.36.0", features = ["sync"] }
tokio-stream = "0.1.15"
uuid = "1.7.0"
thiserror = "1.0.61"

View File

@@ -20,3 +20,4 @@ hyper-rustls = { version = "0.24.0", features = ["http2"] }
uuid = { version = "1.7.0", features = ["v4"] }
tauri = { version = "2.0.0-beta" }
tauri-plugin-shell = "2.0.0-beta"
md5 = "0.7.0"

View File

@@ -16,7 +16,9 @@ use tonic::transport::Uri;
use tonic::{IntoRequest, IntoStreamingRequest, Request, Response, Status, Streaming};
use crate::codec::DynamicCodec;
use crate::proto::{fill_pool, fill_pool_from_files, get_transport, method_desc_to_path};
use crate::proto::{
fill_pool_from_files, fill_pool_from_reflection, get_transport, method_desc_to_path,
};
use crate::{json_schema, MethodDefinition, ServiceDefinition};
#[derive(Clone)]
@@ -182,30 +184,37 @@ impl GrpcHandle {
}
impl GrpcHandle {
pub async fn services_from_files(
pub async fn reflect(
&mut self,
id: &str,
uri: &str,
paths: Vec<PathBuf>,
) -> Result<Vec<ServiceDefinition>, String> {
let pool = fill_pool_from_files(&self.app_handle, paths).await?;
let uri = Uri::from_str(uri).map_err(|e| e.to_string())?;
self.pools.insert(self.get_pool_key(id, &uri), pool.clone());
Ok(self.services_from_pool(&pool))
}
pub async fn services_from_reflection(
&mut self,
id: &str,
uri: &str,
) -> Result<Vec<ServiceDefinition>, String> {
let uri = Uri::from_str(uri).map_err(|e| e.to_string())?;
let pool = fill_pool(&uri).await?;
self.pools.insert(self.get_pool_key(id, &uri), pool.clone());
Ok(self.services_from_pool(&pool))
proto_files: &Vec<PathBuf>,
) -> Result<(), String> {
let pool = if proto_files.is_empty() {
let full_uri = uri_from_str(uri)?;
fill_pool_from_reflection(&full_uri).await
} else {
fill_pool_from_files(&self.app_handle, proto_files).await
}?;
self.pools
.insert(make_pool_key(id, uri, proto_files), pool.clone());
Ok(())
}
fn get_pool_key(&self, id: &str, uri: &Uri) -> String {
format!("{}-{}", id, uri)
pub async fn services(
&mut self,
id: &str,
uri: &str,
proto_files: &Vec<PathBuf>,
) -> Result<Vec<ServiceDefinition>, String> {
// Ensure reflection is up-to-date
self.reflect(id, uri, proto_files).await?;
let pool = self
.get_pool(id, uri, proto_files)
.ok_or("Failed to get pool".to_string())?;
Ok(self.services_from_pool(&pool))
}
fn services_from_pool(&self, pool: &DescriptorPool) -> Vec<ServiceDefinition> {
@@ -237,25 +246,26 @@ impl GrpcHandle {
&mut self,
id: &str,
uri: &str,
proto_files: Vec<PathBuf>,
proto_files: &Vec<PathBuf>,
) -> Result<GrpcConnection, String> {
let uri = Uri::from_str(uri).map_err(|e| e.to_string())?;
let pool = match self.pools.get(id) {
Some(p) => p.clone(),
None => match proto_files.len() {
0 => fill_pool(&uri).await?,
_ => {
let pool = fill_pool_from_files(&self.app_handle, proto_files).await?;
self.pools.insert(id.to_string(), pool.clone());
pool
}
},
};
self.reflect(id, uri, proto_files).await?;
let pool = self
.get_pool(id, uri, proto_files)
.ok_or("Failed to get pool")?;
let uri = uri_from_str(uri)?;
let conn = get_transport();
let connection = GrpcConnection { pool, conn, uri };
let connection = GrpcConnection {
pool: pool.clone(),
conn,
uri,
};
Ok(connection)
}
fn get_pool(&self, id: &str, uri: &str, proto_files: &Vec<PathBuf>) -> Option<&DescriptorPool> {
self.pools.get(make_pool_key(id, uri, proto_files).as_str())
}
}
fn decorate_req<T>(metadata: HashMap<String, String>, req: &mut Request<T>) -> Result<(), String> {
@@ -267,3 +277,28 @@ fn decorate_req<T>(metadata: HashMap<String, String>, req: &mut Request<T>) -> R
}
Ok(())
}
fn uri_from_str(uri_str: &str) -> Result<Uri, String> {
match Uri::from_str(uri_str) {
Ok(uri) => Ok(uri),
Err(err) => {
// Uri::from_str basically only returns "invalid format" so we add more context here
Err(format!("Failed to parse URL, {}", err.to_string()))
}
}
}
fn make_pool_key(id: &str, uri: &str, proto_files: &Vec<PathBuf>) -> String {
let pool_key = format!(
"{}::{}::{}",
id,
uri,
proto_files
.iter()
.map(|p| p.to_string_lossy().to_string())
.collect::<Vec<String>>()
.join(":")
);
format!("{:x}", md5::compute(pool_key))
}

View File

@@ -28,7 +28,7 @@ use tonic_reflection::pb::ServerReflectionRequest;
pub async fn fill_pool_from_files(
app_handle: &AppHandle,
paths: Vec<PathBuf>,
paths: &Vec<PathBuf>,
) -> Result<DescriptorPool, String> {
let mut pool = DescriptorPool::new();
let random_file_name = format!("{}.desc", uuid::Uuid::new_v4());
@@ -121,7 +121,7 @@ pub async fn fill_pool_from_files(
Ok(pool)
}
pub async fn fill_pool(uri: &Uri) -> Result<DescriptorPool, String> {
pub async fn fill_pool_from_reflection(uri: &Uri) -> Result<DescriptorPool, String> {
let mut pool = DescriptorPool::new();
let mut client = ServerReflectionClient::with_origin(get_transport(), uri.clone());

View File

@@ -9,7 +9,6 @@ use std::collections::HashMap;
use std::rc::Rc;
use std::sync::Arc;
use crate::deno_ops::op_yaml_parse;
use anyhow::anyhow;
use anyhow::bail;
use anyhow::Context;
@@ -32,6 +31,9 @@ use deno_core::SourceMapGetter;
use deno_core::{resolve_import, v8};
use tokio::task::block_in_place;
use crate::deno_ops::op_yaml_parse;
use crate::plugin::PluginCapability;
#[derive(Clone)]
struct SourceMapStore(Rc<RefCell<HashMap<String, Vec<u8>>>>);
@@ -134,13 +136,13 @@ impl ModuleLoader for TypescriptModuleLoader {
}
}
pub fn run_plugin_deno_block(
pub fn run_plugin_block(
plugin_index_file: &str,
fn_name: &str,
fn_args: Vec<serde_json::Value>,
) -> Result<serde_json::Value, Error> {
block_in_place(|| {
tauri::async_runtime::block_on(run_plugin_deno_2(plugin_index_file, fn_name, fn_args))
tauri::async_runtime::block_on(run_plugin(plugin_index_file, fn_name, fn_args))
})
}
@@ -151,39 +153,13 @@ deno_core::extension!(
esm = [dir "src/plugin-runtime", "yaml.js"]
);
async fn run_plugin_deno_2(
async fn run_plugin(
plugin_index_file: &str,
fn_name: &str,
fn_args: Vec<serde_json::Value>,
) -> Result<serde_json::Value, Error> {
let source_map_store = SourceMapStore(Rc::new(RefCell::new(HashMap::new())));
let mut ext_console = deno_console::deno_console::init_ops_and_esm();
ext_console.esm_entry_point = Some("ext:deno_console/01_console.js");
let ext_yaak = yaak_runtime::init_ops_and_esm();
let mut js_runtime = JsRuntime::new(RuntimeOptions {
module_loader: Some(Rc::new(TypescriptModuleLoader {
source_maps: source_map_store.clone(),
})),
source_map_getter: Some(Rc::new(source_map_store)),
extensions: vec![ext_console, ext_yaak],
..Default::default()
});
let main_module = resolve_path(
plugin_index_file,
&std::env::current_dir().context("Unable to get CWD")?,
)?;
// Load the main module so we can do stuff with it
let mod_id = js_runtime.load_main_es_module(&main_module).await?;
let result = js_runtime.mod_evaluate(mod_id);
js_runtime.run_event_loop(Default::default()).await?;
result.await?;
let module_namespace = js_runtime.get_module_namespace(mod_id).unwrap();
let mut js_runtime = load_js_runtime()?;
let module_namespace = load_main_module(&mut js_runtime, plugin_index_file).await?;
let scope = &mut js_runtime.handle_scope();
let module_namespace = v8::Local::<v8::Object>::new(scope, module_namespace);
@@ -235,3 +211,95 @@ async fn run_plugin_deno_2(
}
}
}
pub fn get_plugin_capabilities_block(plugin_index_file: &str) -> Result<Vec<PluginCapability>, Error> {
block_in_place(|| tauri::async_runtime::block_on(get_plugin_capabilities(plugin_index_file)))
}
pub async fn get_plugin_capabilities(
plugin_index_file: &str,
) -> Result<Vec<PluginCapability>, Error> {
let mut js_runtime = load_js_runtime()?;
let module_namespace = load_main_module(&mut js_runtime, plugin_index_file).await?;
let scope = &mut js_runtime.handle_scope();
let module_namespace = v8::Local::<v8::Object>::new(scope, module_namespace);
let property_names =
match module_namespace.get_own_property_names(scope, v8::GetPropertyNamesArgs::default()) {
None => return Ok(Vec::new()),
Some(names) => names,
};
let mut capabilities: Vec<PluginCapability> = Vec::new();
for i in 0..property_names.length() {
let name = property_names.get_index(scope, i);
let name = match name {
Some(name) => name,
None => return Ok(Vec::new()),
};
match name.to_rust_string_lossy(scope).as_str() {
"pluginHookImport" => _ = capabilities.push(PluginCapability::Import),
"pluginHookExport" => _ = capabilities.push(PluginCapability::Export),
"pluginHookResponseFilter" => _ = capabilities.push(PluginCapability::Filter),
_ => {}
};
}
Ok(capabilities)
}
async fn load_main_module(
js_runtime: &mut JsRuntime,
plugin_index_file: &str,
) -> Result<v8::Global<v8::Object>, Error> {
let main_module = resolve_path(
plugin_index_file,
&std::env::current_dir().context("Unable to get CWD")?,
)?;
// Load the main module so we can do stuff with it
let mod_id = js_runtime.load_main_es_module(&main_module).await?;
let result = js_runtime.mod_evaluate(mod_id);
js_runtime.run_event_loop(Default::default()).await?;
result.await?;
let module_namespace = js_runtime.get_module_namespace(mod_id).unwrap();
Ok(module_namespace)
}
fn load_js_runtime<'s>() -> Result<JsRuntime, Error> {
let source_map_store = SourceMapStore(Rc::new(RefCell::new(HashMap::new())));
let mut ext_console = deno_console::deno_console::init_ops_and_esm();
ext_console.esm_entry_point = Some("ext:deno_console/01_console.js");
let ext_yaak = yaak_runtime::init_ops_and_esm();
let js_runtime = JsRuntime::new(RuntimeOptions {
module_loader: Some(Rc::new(TypescriptModuleLoader {
source_maps: source_map_store.clone(),
})),
source_map_getter: Some(Rc::new(source_map_store)),
extensions: vec![ext_console, ext_yaak],
..Default::default()
});
// let main_module = resolve_path(
// plugin_index_file.to_str().unwrap(),
// &std::env::current_dir().context("Unable to get CWD")?,
// )?;
//
// // Load the main module so we can do stuff with it
// let mod_id = js_runtime.load_main_es_module(&main_module).await?;
// let result = js_runtime.mod_evaluate(mod_id);
// js_runtime.run_event_loop(Default::default()).await?;
// result.await?;
//
// let module_namespace = js_runtime.get_module_namespace(mod_id).unwrap();
// let scope = &mut js_runtime.handle_scope();
// let module_namespace = v8::Local::<v8::Object>::new(scope, module_namespace);
Ok(js_runtime)
}

View File

@@ -18,6 +18,7 @@ use tauri::{Manager, WebviewWindow};
use tokio::sync::oneshot;
use tokio::sync::watch::Receiver;
use crate::render::variables_from_environment;
use crate::{models, render, response_err};
pub async fn send_http_request(
@@ -33,8 +34,9 @@ pub async fn send_http_request(
let workspace = models::get_workspace(window, &request.workspace_id)
.await
.expect("Failed to get Workspace");
let vars = variables_from_environment(&workspace, environment_ref);
let mut url_string = render::render(&request.url, &workspace, environment.as_ref());
let mut url_string = render::render(&request.url, &vars);
url_string = ensure_proto(&url_string);
if !url_string.starts_with("http://") && !url_string.starts_with("https://") {
@@ -144,8 +146,8 @@ pub async fn send_http_request(
continue;
}
let name = render::render(&h.name, &workspace, environment_ref);
let value = render::render(&h.value, &workspace, environment_ref);
let name = render::render(&h.name, &vars);
let value = render::render(&h.value, &vars);
let header_name = match HeaderName::from_bytes(name.as_bytes()) {
Ok(n) => n,
@@ -180,8 +182,8 @@ pub async fn send_http_request(
.unwrap_or(empty_value)
.as_str()
.unwrap_or("");
let username = render::render(raw_username, &workspace, environment_ref);
let password = render::render(raw_password, &workspace, environment_ref);
let username = render::render(raw_username, &vars);
let password = render::render(raw_password, &vars);
let auth = format!("{username}:{password}");
let encoded = base64::engine::general_purpose::STANDARD_NO_PAD.encode(auth);
@@ -191,7 +193,7 @@ pub async fn send_http_request(
);
} else if b == "bearer" {
let raw_token = a.get("token").unwrap_or(empty_value).as_str().unwrap_or("");
let token = render::render(raw_token, &workspace, environment_ref);
let token = render::render(raw_token, &vars);
headers.insert(
"Authorization",
HeaderValue::from_str(&format!("Bearer {token}")).unwrap(),
@@ -205,8 +207,8 @@ pub async fn send_http_request(
continue;
}
query_params.push((
render::render(&p.name, &workspace, environment_ref),
render::render(&p.value, &workspace, environment_ref),
render::render(&p.name, &vars),
render::render(&p.value, &vars),
));
}
request_builder = request_builder.query(&query_params);
@@ -222,7 +224,7 @@ pub async fn send_http_request(
.unwrap_or(empty_string)
.as_str()
.unwrap_or("");
let body = render::render(raw_text, &workspace, environment_ref);
let body = render::render(raw_text, &vars);
request_builder = request_builder.body(body);
} else if body_type == "application/x-www-form-urlencoded"
&& request_body.contains_key("form")
@@ -249,10 +251,7 @@ pub async fn send_http_request(
.unwrap_or(empty_string)
.as_str()
.unwrap_or_default();
form_params.push((
render::render(name, &workspace, environment_ref),
render::render(value, &workspace, environment_ref),
));
form_params.push((render::render(name, &vars), render::render(value, &vars)));
}
}
request_builder = request_builder.form(&form_params);
@@ -301,13 +300,9 @@ pub async fn send_http_request(
.as_str()
.unwrap_or_default();
let name = render::render(name_raw, &workspace, environment_ref);
let name = render::render(name_raw, &vars);
let mut part = if file_path.is_empty() {
multipart::Part::text(render::render(
value_raw,
&workspace,
environment_ref,
))
multipart::Part::text(render::render(value_raw, &vars))
} else {
match fs::read(file_path) {
Ok(f) => multipart::Part::bytes(f),
@@ -324,7 +319,7 @@ pub async fn send_http_request(
.unwrap_or_default();
if !ct_raw.is_empty() {
let content_type = render::render(ct_raw, &workspace, environment_ref);
let content_type = render::render(ct_raw, &vars);
part = part
.mime_str(content_type.as_str())
.map_err(|e| e.to_string())?;

View File

@@ -53,8 +53,11 @@ use crate::models::{
WorkspaceExportResources,
};
use crate::notifications::YaakNotifier;
use crate::plugin::{run_plugin_export_curl, run_plugin_import, ImportResult};
use crate::render::render_request;
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;
@@ -131,27 +134,21 @@ async fn cmd_grpc_reflect(
let req = get_grpc_request(&window, request_id)
.await
.map_err(|e| e.to_string())?;
let uri = safe_uri(req.url.as_str());
if proto_files.len() > 0 {
grpc_handle
.lock()
.await
.services_from_files(
&req.id,
uri.as_str(),
proto_files
.iter()
.map(|p| PathBuf::from_str(p).unwrap())
.collect(),
)
.await
} else {
grpc_handle
.lock()
.await
.services_from_reflection(&req.id, uri.as_str())
.await
}
let uri = safe_uri(&req.url);
grpc_handle
.lock()
.await
.services(
&req.id,
&uri,
&proto_files
.iter()
.map(|p| PathBuf::from_str(p).unwrap())
.collect(),
)
.await
}
#[tauri::command]
@@ -173,6 +170,7 @@ async fn cmd_grpc_go(
.await
.map_err(|e| e.to_string())?;
let mut metadata = HashMap::new();
let vars = variables_from_environment(&workspace, environment.as_ref());
// Add rest of metadata
for h in req.clone().metadata.0 {
@@ -184,15 +182,14 @@ async fn cmd_grpc_go(
continue;
}
let name = render::render(&h.name, &workspace, environment.as_ref());
let value = render::render(&h.value, &workspace, environment.as_ref());
let name = render::render(&h.name, &vars);
let value = render::render(&h.value, &vars);
metadata.insert(name, value);
}
if let Some(b) = &req.authentication_type {
let req = req.clone();
let environment_ref = environment.as_ref();
let empty_value = &serde_json::to_value("").unwrap();
let a = req.authentication.0;
@@ -207,15 +204,15 @@ async fn cmd_grpc_go(
.unwrap_or(empty_value)
.as_str()
.unwrap_or("");
let username = render::render(raw_username, &workspace, environment_ref);
let password = render::render(raw_password, &workspace, environment_ref);
let username = render::render(raw_username, &vars);
let password = render::render(raw_password, &vars);
let auth = format!("{username}:{password}");
let encoded = base64::engine::general_purpose::STANDARD_NO_PAD.encode(auth);
metadata.insert("Authorization".to_string(), format!("Basic {}", encoded));
} else if b == "bearer" {
let raw_token = a.get("token").unwrap_or(empty_value).as_str().unwrap_or("");
let token = render::render(raw_token, &workspace, environment_ref);
let token = render::render(raw_token, &vars);
metadata.insert("Authorization".to_string(), format!("Bearer {token}"));
}
}
@@ -228,6 +225,7 @@ async fn cmd_grpc_go(
workspace_id: req.workspace_id,
request_id: req.id,
status: -1,
elapsed: 0,
url: req.url.clone(),
..Default::default()
},
@@ -235,6 +233,7 @@ async fn cmd_grpc_go(
.await
.map_err(|e| e.to_string())?
};
let conn_id = conn.id.clone();
let base_msg = GrpcEvent {
@@ -267,12 +266,29 @@ async fn cmd_grpc_go(
.connect(
&req.clone().id,
uri.as_str(),
proto_files
&proto_files
.iter()
.map(|p| PathBuf::from_str(p).unwrap())
.collect(),
)
.await?;
.await;
let connection = match connection {
Ok(c) => c,
Err(err) => {
upsert_grpc_connection(
&w,
&GrpcConnection {
elapsed: start.elapsed().as_millis() as i64,
error: Some(err.clone()),
..conn.clone()
},
)
.await
.map_err(|e| e.to_string())?;
return Ok(conn_id);
}
};
let method_desc = connection
.method(&service, &method)
@@ -287,11 +303,10 @@ async fn cmd_grpc_go(
let cb = {
let cancelled_rx = cancelled_rx.clone();
let environment = environment.clone();
let workspace = workspace.clone();
let w = w.clone();
let base_msg = base_msg.clone();
let method_desc = method_desc.clone();
let vars = vars.clone();
move |ev: tauri::Event| {
if *cancelled_rx.borrow() {
@@ -314,9 +329,8 @@ async fn cmd_grpc_go(
Ok(IncomingMsg::Message(raw_msg)) => {
let w = w.clone();
let base_msg = base_msg.clone();
let environment_ref = environment.as_ref();
let method_desc = method_desc.clone();
let msg = render::render(raw_msg.as_str(), &workspace, environment_ref);
let msg = render::render(raw_msg.as_str(), &vars);
let d_msg: DynamicMessage = match deserialize_message(msg.as_str(), method_desc)
{
Ok(d_msg) => d_msg,
@@ -368,14 +382,13 @@ async fn cmd_grpc_go(
let w = w.clone();
let base_event = base_msg.clone();
let req = req.clone();
let workspace = workspace.clone();
let environment = environment.clone();
let vars = vars.clone();
let raw_msg = if req.message.is_empty() {
"{}".to_string()
} else {
req.message
};
let msg = render::render(&raw_msg, &workspace, environment.as_ref());
let msg = render::render(&raw_msg, &vars);
upsert_grpc_event(
&w,
@@ -735,7 +748,11 @@ async fn cmd_filter_response(
};
let body = read_to_string(response.body_path.unwrap()).unwrap();
let filter_result = plugin::run_plugin_filter(&w.app_handle(), plugin_name, filter, &body)
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)
.await
.expect("Failed to run filter");
Ok(filter_result.filtered)
@@ -748,26 +765,23 @@ async fn cmd_import_data(
_workspace_id: &str,
) -> Result<WorkspaceExportResources, String> {
let mut result: Option<ImportResult> = None;
let plugins = vec![
"importer-postman",
"importer-insomnia",
"importer-yaak",
"importer-curl",
];
let file =
read_to_string(file_path).unwrap_or_else(|_| panic!("Unable to read file {}", file_path));
let file_contents = file.as_str();
for plugin_name in plugins {
let v = run_plugin_import(&w.app_handle(), plugin_name, file_contents)
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)
.await
.map_err(|e| e.to_string())?;
if let Some(r) = v {
info!("Imported data using {}", plugin_name);
info!("Imported data using {}", plugin.name);
analytics::track_event(
&w.app_handle(),
AnalyticsResource::App,
AnalyticsAction::Import,
Some(json!({ "plugin": plugin_name })),
Some(json!({ "plugin": plugin.name })),
)
.await;
result = Some(r);
@@ -907,10 +921,17 @@ async fn cmd_request_to_curl(
}
#[tauri::command]
async fn cmd_curl_to_request(app_handle: AppHandle, command: &str, workspace_id: &str) -> Result<HttpRequest, String> {
let v = run_plugin_import(&app_handle, "importer-curl", command)
.await
.map_err(|e| e.to_string());
async fn cmd_curl_to_request(
app_handle: AppHandle,
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

View File

@@ -1,6 +1,6 @@
use std::time::SystemTime;
use chrono::{Duration, NaiveDateTime, Utc};
use chrono::{DateTime, Duration, Utc};
use log::debug;
use reqwest::Method;
use serde::{Deserialize, Serialize};
@@ -23,7 +23,7 @@ pub struct YaakNotifier {
#[derive(sqlx::FromRow, Debug, Clone, Serialize, Deserialize, Default)]
#[serde(default, rename_all = "camelCase")]
pub struct YaakNotification {
timestamp: NaiveDateTime,
timestamp: DateTime<Utc>,
id: String,
message: String,
action: Option<YaakNotificationAction>,
@@ -77,7 +77,7 @@ impl YaakNotifier {
let age = notification
.timestamp
.signed_duration_since(Utc::now().naive_utc());
.signed_duration_since(Utc::now());
let seen = get_kv(app).await?;
if seen.contains(&notification.id) || (age > Duration::days(1)) {
debug!("Already seen notification {}", notification.id);

View File

@@ -1,13 +1,24 @@
use std::path;
use std::{fs, io};
use log::error;
use serde::{Deserialize, Serialize};
use tauri::path::BaseDirectory;
use tauri::{AppHandle, Manager};
use thiserror::Error;
use crate::deno::run_plugin_deno_block;
use crate::deno::{get_plugin_capabilities_block, run_plugin_block};
use crate::models::{HttpRequest, WorkspaceExportResources};
#[derive(Error, Debug)]
pub enum PluginError {
#[error("directory not found")]
DirectoryNotFound(#[from] io::Error),
#[error("anyhow error")]
V8(#[from] anyhow::Error),
// #[error("unknown data store error")]
// Unknown,
}
#[derive(Default, Debug, Deserialize, Serialize)]
pub struct FilterResult {
pub filtered: String,
@@ -18,21 +29,71 @@ pub struct ImportResult {
pub resources: WorkspaceExportResources,
}
pub async fn run_plugin_filter(
#[derive(Eq, PartialEq, Hash, Clone)]
pub enum PluginCapability {
Export,
Import,
Filter,
}
pub struct PluginDef {
pub name: String,
pub path: String,
pub capabilities: Vec<PluginCapability>,
}
pub fn scan_plugins(app_handle: &AppHandle) -> Result<Vec<PluginDef>, PluginError> {
let plugins_dir = app_handle
.path()
.resolve("plugins", BaseDirectory::Resource)
.expect("failed to resolve plugin directory resource");
let plugin_entries = fs::read_dir(plugins_dir)?;
let mut plugins = Vec::new();
for entry in plugin_entries {
let plugin_dir_entry = match entry {
Err(_) => continue,
Ok(entry) => entry,
};
let plugin_index_file = plugin_dir_entry.path().join("index.mjs");
let capabilities = get_plugin_capabilities_block(&plugin_index_file.to_str().unwrap())?;
plugins.push(PluginDef {
name: plugin_dir_entry.file_name().to_string_lossy().to_string(),
path: plugin_index_file.to_string_lossy().to_string(),
capabilities,
});
}
Ok(plugins)
}
pub async fn find_plugins(
app_handle: &AppHandle,
plugin_name: &str,
capability: &PluginCapability,
) -> Result<Vec<PluginDef>, PluginError> {
let plugins = scan_plugins(app_handle)?
.into_iter()
.filter(|p| p.capabilities.contains(capability))
.collect();
Ok(plugins)
}
pub fn get_plugin(app_handle: &AppHandle, name: &str) -> Result<Option<PluginDef>, PluginError> {
Ok(scan_plugins(app_handle)?
.into_iter()
.find(|p| p.name == name))
}
pub async fn run_plugin_filter(
plugin: &PluginDef,
response_body: &str,
filter: &str,
) -> Option<FilterResult> {
let plugin_dir = app_handle
.path()
.resolve("plugins", BaseDirectory::Resource)
.expect("failed to resolve plugin directory resource")
.join(plugin_name);
let plugin_index_file = plugin_dir.join("index.mjs");
let result = run_plugin_deno_block(
plugin_index_file.to_str().unwrap(),
let result = run_plugin_block(
&plugin.path,
"pluginHookResponseFilter",
vec![
serde_json::to_value(response_body).unwrap(),
@@ -43,7 +104,7 @@ pub async fn run_plugin_filter(
.expect("Failed to run plugin");
if result.is_null() {
error!("Plugin {} failed to run", plugin_name);
error!("Plugin {} failed to run", plugin.name);
return None;
}
@@ -56,39 +117,25 @@ pub fn run_plugin_export_curl(
app_handle: &AppHandle,
request: &HttpRequest,
) -> Result<String, String> {
let plugin_dir = app_handle
.path()
.resolve("plugins", BaseDirectory::Resource)
.expect("failed to resolve plugin directory resource")
.join("exporter-curl");
let plugin_index_file = plugin_dir.join("index.mjs");
let plugin = match get_plugin(app_handle, "exporter-curl").map_err(|e| e.to_string())? {
None => return Err("Failed to get plugin".into()),
Some(p) => p,
};
let request_json = serde_json::to_value(request).map_err(|e| e.to_string())?;
let result = run_plugin_deno_block(
plugin_index_file.to_str().unwrap(),
"pluginHookExport",
vec![request_json],
)
.map_err(|e| e.to_string())?;
let result = run_plugin_block(&plugin.path, "pluginHookExport", vec![request_json])
.map_err(|e| e.to_string())?;
let export_str: String = serde_json::from_value(result).map_err(|e| e.to_string())?;
Ok(export_str)
}
pub async fn run_plugin_import(
app_handle: &AppHandle,
plugin_name: &str,
plugin: &PluginDef,
file_contents: &str,
) -> Result<Option<ImportResult>, String> {
let plugin_dir = app_handle
.path()
.resolve("plugins", BaseDirectory::Resource)
.expect("failed to resolve plugin directory resource")
.join(plugin_name);
let plugin_index_file = plugin_dir.join("index.mjs");
let result = run_plugin_deno_block(
plugin_index_file.to_str().unwrap(),
let result = run_plugin_block(
&plugin.path,
"pluginHookImport",
vec![serde_json::to_value(file_contents).map_err(|e| e.to_string())?],
)

View File

@@ -9,16 +9,18 @@ use templates::parse_and_render;
pub fn render_request(r: &HttpRequest, w: &Workspace, e: Option<&Environment>) -> HttpRequest {
let r = r.clone();
let vars = &variables_from_environment(w, e);
HttpRequest {
url: render(r.url.as_str(), w, e),
url: render(r.url.as_str(), vars),
url_parameters: Json(
r.url_parameters
.0
.iter()
.map(|p| HttpUrlParameter {
enabled: p.enabled,
name: render(p.name.as_str(), w, e),
value: render(p.value.as_str(), w, e),
name: render(p.name.as_str(), vars),
value: render(p.value.as_str(), vars),
})
.collect::<Vec<HttpUrlParameter>>(),
),
@@ -28,8 +30,8 @@ pub fn render_request(r: &HttpRequest, w: &Workspace, e: Option<&Environment>) -
.iter()
.map(|p| HttpRequestHeader {
enabled: p.enabled,
name: render(p.name.as_str(), w, e),
value: render(p.value.as_str(), w, e),
name: render(p.name.as_str(), vars),
value: render(p.value.as_str(), vars),
})
.collect::<Vec<HttpRequestHeader>>(),
),
@@ -39,11 +41,11 @@ pub fn render_request(r: &HttpRequest, w: &Workspace, e: Option<&Environment>) -
.iter()
.map(|(k, v)| {
let v = if v.is_string() {
render(v.as_str().unwrap(), w, e)
render(v.as_str().unwrap(), vars)
} else {
v.to_string()
};
(render(k, w, e), JsonValue::from(v))
(render(k, vars), JsonValue::from(v))
})
.collect::<HashMap<String, JsonValue>>(),
),
@@ -53,11 +55,11 @@ pub fn render_request(r: &HttpRequest, w: &Workspace, e: Option<&Environment>) -
.iter()
.map(|(k, v)| {
let v = if v.is_string() {
render(v.as_str().unwrap(), w, e)
render(v.as_str().unwrap(), vars)
} else {
v.to_string()
};
(render(k, w, e), JsonValue::from(v))
(render(k, vars), JsonValue::from(v))
})
.collect::<HashMap<String, JsonValue>>(),
),
@@ -65,7 +67,31 @@ pub fn render_request(r: &HttpRequest, w: &Workspace, e: Option<&Environment>) -
}
}
pub fn render(template: &str, workspace: &Workspace, environment: Option<&Environment>) -> String {
pub fn recursively_render_variables<'s>(
m: &HashMap<String, String>,
render_count: usize,
) -> HashMap<String, String> {
let mut did_render = false;
let mut new_map = m.clone();
for (k, v) in m.clone() {
let rendered = render(v.as_str(), m);
if rendered != v {
did_render = true
}
new_map.insert(k, rendered);
}
if did_render && render_count <= 3 {
new_map = recursively_render_variables(&new_map, render_count + 1);
}
new_map
}
pub fn variables_from_environment(
workspace: &Workspace,
environment: Option<&Environment>,
) -> HashMap<String, String> {
let mut variables = HashMap::new();
variables = add_variable_to_map(variables, &workspace.variables.0);
@@ -73,13 +99,17 @@ pub fn render(template: &str, workspace: &Workspace, environment: Option<&Enviro
variables = add_variable_to_map(variables, &e.variables.0);
}
parse_and_render(template, variables, None)
recursively_render_variables(&variables, 0)
}
fn add_variable_to_map<'a>(
m: HashMap<&'a str, &'a str>,
variables: &'a Vec<EnvironmentVariable>,
) -> HashMap<&'a str, &'a str> {
pub fn render(template: &str, vars: &HashMap<String, String>) -> String {
parse_and_render(template, vars, None)
}
fn add_variable_to_map(
m: HashMap<String, String>,
variables: &Vec<EnvironmentVariable>,
) -> HashMap<String, String> {
let mut map = m.clone();
for variable in variables {
if !variable.enabled || variable.value.is_empty() {
@@ -87,7 +117,7 @@ fn add_variable_to_map<'a>(
}
let name = variable.name.as_str();
let value = variable.value.as_str();
map.insert(name, value);
map.insert(name.into(), value.into());
}
map

View File

@@ -1,6 +1,6 @@
{
"productName": "yaak",
"version": "2024.6.4",
"version": "0.0.0",
"identifier": "app.yaak.desktop",
"build": {
"beforeBuildCommand": "npm run build",
@@ -27,7 +27,6 @@
"desktop": {
"schemes": [
"yaak"
]
}
},

View File

@@ -6,7 +6,7 @@ type TemplateCallback = fn(name: &str, args: Vec<String>) -> String;
pub fn parse_and_render(
template: &str,
vars: HashMap<&str, &str>,
vars: &HashMap<String, String>,
cb: Option<TemplateCallback>,
) -> String {
let mut p = Parser::new(template);
@@ -16,7 +16,7 @@ pub fn parse_and_render(
pub fn render(
tokens: Vec<Token>,
vars: HashMap<&str, &str>,
vars: &HashMap<String, String>,
cb: Option<TemplateCallback>,
) -> String {
let mut doc_str: Vec<String> = Vec::new();
@@ -24,7 +24,7 @@ pub fn render(
for t in tokens {
match t {
Token::Raw(s) => doc_str.push(s),
Token::Tag(val) => doc_str.push(render_tag(val, vars.clone(), cb)),
Token::Tag(val) => doc_str.push(render_tag(val, &vars, cb)),
Token::Eof => {}
}
}
@@ -32,9 +32,9 @@ pub fn render(
return doc_str.join("");
}
fn render_tag<'s>(
fn render_tag(
val: Val,
vars: HashMap<&'s str, &'s str>,
vars: &HashMap<String, String>,
cb: Option<TemplateCallback>,
) -> String {
match val {
@@ -44,13 +44,13 @@ fn render_tag<'s>(
None => "".into(),
},
Val::Fn { name, args } => {
let empty = &"";
let empty = "".to_string();
let resolved_args = args
.iter()
.map(|a| match a {
Val::Str(s) => s.to_string(),
Val::Var(i) => vars.get(i.as_str()).unwrap_or(empty).to_string(),
val => render_tag(val.clone(), vars.clone(), cb),
Val::Var(i) => vars.get(i.as_str()).unwrap_or(&empty).to_string(),
val => render_tag(val.clone(), vars, cb),
})
.collect::<Vec<String>>();
match cb {
@@ -72,7 +72,7 @@ mod tests {
let template = "";
let vars = HashMap::new();
let result = "";
assert_eq!(parse_and_render(template, vars, None), result.to_string());
assert_eq!(parse_and_render(template, &vars, None), result.to_string());
}
#[test]
@@ -80,23 +80,23 @@ mod tests {
let template = "Hello World!";
let vars = HashMap::new();
let result = "Hello World!";
assert_eq!(parse_and_render(template, vars, None), result.to_string());
assert_eq!(parse_and_render(template, &vars, None), result.to_string());
}
#[test]
fn render_simple() {
let template = "${[ foo ]}";
let vars = HashMap::from([("foo", "bar")]);
let vars = HashMap::from([("foo".to_string(), "bar".to_string())]);
let result = "bar";
assert_eq!(parse_and_render(template, vars, None), result.to_string());
assert_eq!(parse_and_render(template, &vars, None), result.to_string());
}
#[test]
fn render_surrounded() {
let template = "hello ${[ word ]} world!";
let vars = HashMap::from([("word", "cruel")]);
let vars = HashMap::from([("word".to_string(), "cruel".to_string())]);
let result = "hello cruel world!";
assert_eq!(parse_and_render(template, vars, None), result.to_string());
assert_eq!(parse_and_render(template, &vars, None), result.to_string());
}
#[test]
@@ -108,7 +108,7 @@ mod tests {
fn cb(name: &str, args: Vec<String>) -> String {
format!("{name}: {:?}", args)
}
assert_eq!(parse_and_render(template, vars, Some(cb)), result);
assert_eq!(parse_and_render(template, &vars, Some(cb)), result);
}
#[test]
@@ -125,7 +125,7 @@ mod tests {
}
assert_eq!(
parse_and_render(template, vars, Some(cb)),
parse_and_render(template, &vars, Some(cb)),
result.to_string()
);
}

View File

@@ -1,6 +1,7 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { MotionConfig } from 'framer-motion';
import { Suspense } from 'react';
import React, { Suspense } from 'react';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import { HelmetProvider } from 'react-helmet-async';
@@ -19,12 +20,12 @@ const queryClient = new QueryClient({
export function App() {
return (
<QueryClientProvider client={queryClient}>
<ReactQueryDevtools initialIsOpen={false} />
<MotionConfig transition={{ duration: 0.1 }}>
<HelmetProvider>
<DndProvider backend={HTML5Backend}>
<Suspense>
<AppRouter />
{/*<ReactQueryDevtools initialIsOpen={false} />*/}
</Suspense>
</DndProvider>
</HelmetProvider>

View File

@@ -1,4 +1,3 @@
import { invoke } from '@tauri-apps/api/core';
import classNames from 'classnames';
import { search } from 'fast-fuzzy';
import type { KeyboardEvent, ReactNode } from 'react';
@@ -25,6 +24,7 @@ import { useRequests } from '../hooks/useRequests';
import { useSidebarHidden } from '../hooks/useSidebarHidden';
import { useWorkspaces } from '../hooks/useWorkspaces';
import { fallbackRequestName } from '../lib/fallbackRequestName';
import { invokeCmd } from '../lib/tauri';
import { CookieDialog } from './CookieDialog';
import { Button } from './core/Button';
import { Heading } from './core/Heading';
@@ -82,7 +82,7 @@ export function CommandPalette({ onClose }: { onClose: () => void }) {
action: 'settings.show',
onSelect: async () => {
if (workspaceId == null) return;
await invoke('cmd_new_nested_window', {
await invokeCmd('cmd_new_nested_window', {
url: routes.paths.workspaceSettings({ workspaceId }),
label: 'settings',
title: 'Yaak Settings',
@@ -278,7 +278,7 @@ export function CommandPalette({ onClose }: { onClose: () => void }) {
workspaceGroup.items.push({
key: `switch-workspace-${w.id}`,
label: w.name,
onSelect: () => openWorkspace.mutate({ workspace: w, inNewWindow: false }),
onSelect: () => openWorkspace.mutate({ workspaceId: w.id, inNewWindow: false }),
});
}

View File

@@ -193,7 +193,7 @@ const EnvironmentEditor = function ({
namePlaceholder="VAR_NAME"
nameValidate={validateName}
valueType={valueVisibility.value ? 'text' : 'password'}
valueAutocompleteVariables={false}
valueAutocompleteVariables={true}
forceUpdateKey={environment?.id ?? workspace?.id ?? 'n/a'}
pairs={variables}
onChange={handleChange}

View File

@@ -1,9 +1,9 @@
import { invoke } from '@tauri-apps/api/core';
import { save } from '@tauri-apps/plugin-dialog';
import { useCallback, useMemo, useState } from 'react';
import slugify from 'slugify';
import type { Workspace } from '../lib/models';
import { count } from '../lib/pluralize';
import { invokeCmd } from '../lib/tauri';
import { Button } from './core/Button';
import { Checkbox } from './core/Checkbox';
import { HStack, VStack } from './core/Stacks';
@@ -49,7 +49,7 @@ export function ExportDataDialog({
return;
}
await invoke('cmd_export_data', { workspaceIds: ids, exportPath });
await invokeCmd('cmd_export_data', { workspaceIds: ids, exportPath });
onHide();
onSuccess(exportPath);
}, [onHide, onSuccess, selectedWorkspaces, workspaces]);

View File

@@ -1,4 +1,3 @@
import { updateSchema } from 'cm6-graphql';
import type { EditorView } from 'codemirror';
import { useCallback, useEffect, useMemo, useRef } from 'react';
import { useIntrospectGraphQL } from '../hooks/useIntrospectGraphQL';
@@ -10,6 +9,7 @@ import { Editor, formatGraphQL } from './core/Editor';
import { FormattedError } from './core/FormattedError';
import { Separator } from './core/Separator';
import { useDialog } from './DialogContext';
import { updateSchema } from 'cm6-graphql';
type Props = Pick<
EditorProps,
@@ -43,7 +43,13 @@ export function GraphQLEditor({ defaultValue, onChange, baseRequest, ...extraEdi
}, [defaultValue]);
const handleChange = useCallback(
(b: GraphQLBody) => onChange?.(JSON.stringify(b, null, 2)),
(b: GraphQLBody) => {
try {
onChange?.(JSON.stringify(b, null, 2));
} catch (err) {
// Meh, not much we can do here
}
},
[onChange],
);
@@ -53,24 +59,65 @@ export function GraphQLEditor({ defaultValue, onChange, baseRequest, ...extraEdi
);
const handleChangeVariables = useCallback(
(variables: string) => {
try {
handleChange({ query, variables: JSON.parse(variables) });
} catch (e) {
// Meh, not much we can do here
}
},
(variables: string) => handleChange({ query, variables: JSON.parse(variables) }),
[handleChange, query],
);
// Refetch the schema when the URL changes
useEffect(() => {
if (editorViewRef.current === null) return;
updateSchema(editorViewRef.current, schema);
updateSchema(editorViewRef.current, schema ?? undefined);
}, [schema]);
const dialog = useDialog();
const actions = useMemo<EditorProps['actions']>(() => {
const isValid = error || isLoading;
if (!isValid) {
return [];
}
const actions: EditorProps['actions'] = [
<div key="introspection" className="!opacity-100">
<Button
key="introspection"
size="xs"
color={error ? 'danger' : 'secondary'}
isLoading={isLoading}
onClick={() => {
dialog.show({
title: 'Introspection Failed',
size: 'dynamic',
id: 'introspection-failed',
render: () => (
<>
<FormattedError>{error ?? 'unknown'}</FormattedError>
<div className="w-full my-4">
<Button
onClick={() => {
dialog.hide('introspection-failed');
refetch();
}}
className="ml-auto"
color="primary"
size="sm"
>
Try Again
</Button>
</div>
</>
),
});
}}
>
{error ? 'Introspection Failed' : 'Introspecting'}
</Button>
</div>,
];
return actions;
}, [dialog, error, isLoading, refetch]);
return (
<div className="h-full w-full grid grid-cols-1 grid-rows-[minmax(0,100%)_auto]">
<Editor
@@ -81,47 +128,7 @@ export function GraphQLEditor({ defaultValue, onChange, baseRequest, ...extraEdi
onChange={handleChangeQuery}
placeholder="..."
ref={editorViewRef}
actions={
error || isLoading
? [
<div key="introspection" className="!opacity-100">
<Button
key="introspection"
size="xs"
color={error ? 'danger' : 'secondary'}
isLoading={isLoading}
onClick={() => {
dialog.show({
title: 'Introspection Failed',
size: 'dynamic',
id: 'introspection-failed',
render: () => (
<>
<FormattedError>{error ?? 'unknown'}</FormattedError>
<div className="w-full my-4">
<Button
onClick={() => {
dialog.hide('introspection-failed');
refetch();
}}
className="ml-auto"
color="primary"
size="sm"
>
Try Again
</Button>
</div>
</>
),
});
}}
>
{error ? 'Introspection Failed' : 'Introspecting'}
</Button>
</div>,
]
: []
}
actions={actions}
{...extraEditorProps}
/>
<div className="grid grid-rows-[auto_minmax(0,1fr)] grid-cols-1 min-h-[5rem]">

View File

@@ -17,14 +17,16 @@ interface Props {
style: CSSProperties;
}
const emptyArray: string[] = [];
export function GrpcConnectionLayout({ style }: Props) {
const activeRequest = useActiveRequest('grpc_request');
const updateRequest = useUpdateGrpcRequest(activeRequest?.id ?? null);
const { mutateAsync: updateRequest } = useUpdateGrpcRequest(activeRequest?.id ?? null);
const connections = useGrpcConnections(activeRequest?.id ?? null);
const activeConnection = connections[0] ?? null;
const messages = useGrpcEvents(activeConnection?.id ?? null);
const protoFilesKv = useGrpcProtoFiles(activeRequest?.id ?? null);
const protoFiles = protoFilesKv.value ?? [];
const protoFiles = protoFilesKv.value ?? emptyArray;
const grpc = useGrpc(activeRequest, activeConnection, protoFiles);
const services = grpc.reflect.data ?? null;
@@ -32,7 +34,7 @@ export function GrpcConnectionLayout({ style }: Props) {
if (services == null || activeRequest == null) return;
const s = services.find((s) => s.name === activeRequest.service);
if (s == null) {
updateRequest.mutate({
updateRequest({
service: services[0]?.name ?? null,
method: services[0]?.methods[0]?.name ?? null,
});
@@ -41,7 +43,7 @@ export function GrpcConnectionLayout({ style }: Props) {
const m = s.methods.find((m) => m.name === activeRequest.method);
if (m == null) {
updateRequest.mutate({ method: s.methods[0]?.name ?? null });
updateRequest({ method: s.methods[0]?.name ?? null });
return;
}
}, [activeRequest, services, updateRequest]);
@@ -84,6 +86,7 @@ export function GrpcConnectionLayout({ style }: Props) {
activeRequest={activeRequest}
protoFiles={protoFiles}
methodType={methodType}
isStreaming={grpc.isStreaming}
onGo={grpc.go.mutate}
onCommit={grpc.commit.mutate}
onCancel={grpc.cancel.mutate}

View File

@@ -6,6 +6,8 @@ import { useGrpcEvents } from '../hooks/useGrpcEvents';
import { usePinnedGrpcConnection } from '../hooks/usePinnedGrpcConnection';
import { useStateWithDeps } from '../hooks/useStateWithDeps';
import type { GrpcEvent, GrpcRequest } from '../lib/models';
import { isResponseLoading } from '../lib/models';
import { Banner } from './core/Banner';
import { Button } from './core/Button';
import { Icon } from './core/Icon';
import { JsonAttributeTree } from './core/JsonAttributeTree';
@@ -64,7 +66,7 @@ export function GrpcConnectionMessagesPane({ style, methodType, activeRequest }:
<HStack className="pl-3 mb-1 font-mono">
<HStack space={2}>
<span>{events.length} messages</span>
{activeConnection.elapsed === 0 && (
{isResponseLoading(activeConnection) && (
<Icon icon="refresh" size="sm" spin className="text-fg-subtler" />
)}
</HStack>
@@ -75,6 +77,11 @@ export function GrpcConnectionMessagesPane({ style, methodType, activeRequest }:
/>
</HStack>
<div className="overflow-y-auto h-full">
{activeConnection.error && (
<Banner color="danger" className="m-3">
{activeConnection.error}
</Banner>
)}
{...events.map((e) => (
<EventRow
key={e.id}

View File

@@ -4,7 +4,6 @@ import type { CSSProperties } from 'react';
import React, { useCallback, useMemo, useRef, useState } from 'react';
import { createGlobalState } from 'react-use';
import type { ReflectResponseService } from '../hooks/useGrpc';
import { useGrpcConnections } from '../hooks/useGrpcConnections';
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
import { useUpdateGrpcRequest } from '../hooks/useUpdateGrpcRequest';
import type { GrpcMetadataEntry, GrpcRequest } from '../lib/models';
@@ -37,6 +36,7 @@ interface Props {
| 'streaming'
| 'no-schema'
| 'no-method';
isStreaming: boolean;
onCommit: () => void;
onCancel: () => void;
onSend: (v: { message: string }) => void;
@@ -54,15 +54,13 @@ export function GrpcConnectionSetupPane({
protoFiles,
reflectionError,
reflectionLoading,
isStreaming,
onGo,
onCommit,
onCancel,
onSend,
}: Props) {
const connections = useGrpcConnections(activeRequest.id ?? null);
const updateRequest = useUpdateGrpcRequest(activeRequest?.id ?? null);
const activeConnection = connections[0] ?? null;
const isStreaming = activeConnection?.elapsed === 0;
const [activeTab, setActiveTab] = useActiveTab();
const { updateKey: forceUpdateKey } = useRequestUpdateKey(activeRequest.id ?? null);
@@ -78,7 +76,9 @@ export function GrpcConnectionSetupPane({
);
const handleChangeMessage = useCallback(
(message: string) => updateRequest.mutateAsync({ message }),
(message: string) => {
return updateRequest.mutateAsync({ message });
},
[updateRequest],
);
@@ -221,14 +221,14 @@ export function GrpcConnectionSetupPane({
{isStreaming && (
<>
<IconButton
className="border border-background-highlight-secondary"
variant="border"
size="sm"
title="Cancel"
onClick={onCancel}
icon="x"
/>
<IconButton
className="border border-background-highlight-secondary"
variant="border"
size="sm"
title="Commit"
onClick={onCommit}
@@ -237,8 +237,8 @@ export function GrpcConnectionSetupPane({
</>
)}
<IconButton
className="border border-background-highlight-secondary"
size="sm"
variant="border"
title={isStreaming ? 'Connect' : 'Send'}
hotkeyAction="grpc_request.send"
onClick={isStreaming ? handleSend : handleConnect}
@@ -247,8 +247,8 @@ export function GrpcConnectionSetupPane({
</>
) : (
<IconButton
className="border border-background-highlight-secondary"
size="sm"
variant="border"
title={methodType === 'unary' ? 'Send' : 'Connect'}
hotkeyAction="grpc_request.send"
onClick={isStreaming ? onCancel : handleConnect}

View File

@@ -6,8 +6,8 @@ import {
handleRefresh,
jsonCompletion,
jsonSchemaLinter,
stateExtensions,
updateSchema,
stateExtensions,
} from 'codemirror-json-schema';
import { useEffect, useMemo, useRef } from 'react';
import { useAlert } from '../hooks/useAlert';
@@ -41,6 +41,7 @@ export function GrpcEditor({
...extraEditorProps
}: Props) {
const editorViewRef = useRef<EditorView>(null);
const alert = useAlert();
const dialog = useDialog();

View File

@@ -5,7 +5,6 @@ import { useGrpcRequest } from '../hooks/useGrpcRequest';
import { count } from '../lib/pluralize';
import { Banner } from './core/Banner';
import { Button } from './core/Button';
import { FormattedError } from './core/FormattedError';
import { IconButton } from './core/IconButton';
import { InlineCode } from './core/InlineCode';
import { Link } from './core/Link';
@@ -129,7 +128,14 @@ export function GrpcProtoSelection({ requestId }: Props) {
</tbody>
</table>
)}
{reflectError && <FormattedError>{reflectError}</FormattedError>}
{reflectError && (
<Banner color="warning">
<h1 className="font-bold">
Reflection failed on URL <InlineCode>{request.url}</InlineCode>
</h1>
{reflectError}
</Banner>
)}
{reflectionUnimplemented && protoFiles.length === 0 && (
<Banner>
<InlineCode>{request.url}</InlineCode> doesn&apos;t implement{' '}

View File

@@ -31,7 +31,7 @@ export function OpenWorkspaceDialog({ hide, workspace }: Props) {
color="primary"
onClick={() => {
hide();
openWorkspace.mutate({ workspace, inNewWindow: false });
openWorkspace.mutate({ workspaceId: workspace.id, inNewWindow: false });
if (remember) {
updateSettings.mutate({ openWorkspaceNewWindow: false });
}
@@ -45,7 +45,7 @@ export function OpenWorkspaceDialog({ hide, workspace }: Props) {
rightSlot={<Icon icon="externalLink" />}
onClick={() => {
hide();
openWorkspace.mutate({ workspace, inNewWindow: true });
openWorkspace.mutate({ workspaceId: workspace.id, inNewWindow: true });
if (remember) {
updateSettings.mutate({ openWorkspaceNewWindow: true });
}

View File

@@ -1,9 +1,9 @@
import { invoke } from '@tauri-apps/api/core';
import { open } from '@tauri-apps/plugin-dialog';
import React, { useState } from 'react';
import { useLocalStorage } from 'react-use';
import { useThemes } from '../../hooks/useThemes';
import { capitalize } from '../../lib/capitalize';
import { invokeCmd } from '../../lib/tauri';
import { yaakDark } from '../../lib/theme/themes/yaak';
import { getThemeCSS } from '../../lib/theme/window';
import { Banner } from '../core/Banner';
@@ -58,11 +58,11 @@ export function SettingsDesign() {
const coreThemeCSS = [yaakDark].map(getThemeCSS).join('\n\n');
try {
await invoke('cmd_write_file_dev', {
await invokeCmd('cmd_write_file_dev', {
pathname: exportDir + '/themes-all.css',
contents: allThemesCSS,
});
await invoke('cmd_write_file_dev', {
await invokeCmd('cmd_write_file_dev', {
pathname: exportDir + '/themes-slim.css',
contents: coreThemeCSS,
});

View File

@@ -1,4 +1,3 @@
import { invoke } from '@tauri-apps/api/core';
import { open } from '@tauri-apps/plugin-shell';
import { useRef } from 'react';
import { useActiveWorkspaceId } from '../hooks/useActiveWorkspaceId';
@@ -8,6 +7,7 @@ import { useCheckForUpdates } from '../hooks/useCheckForUpdates';
import { useExportData } from '../hooks/useExportData';
import { useImportData } from '../hooks/useImportData';
import { useListenToTauriEvent } from '../hooks/useListenToTauriEvent';
import { invokeCmd } from '../lib/tauri';
import type { DropdownRef } from './core/Dropdown';
import { Dropdown } from './core/Dropdown';
import { Icon } from './core/Icon';
@@ -27,7 +27,7 @@ export function SettingsDropdown() {
const showSettings = async () => {
if (!workspaceId) return;
await invoke('cmd_new_nested_window', {
await invokeCmd('cmd_new_nested_window', {
url: routes.paths.workspaceSettings({ workspaceId }),
label: 'settings',
title: 'Yaak Settings',

View File

@@ -838,7 +838,7 @@ const SidebarItem = forwardRef(function SidebarItem(
</div>
{latestGrpcConnection ? (
<div className="ml-auto">
{latestGrpcConnection.elapsed === 0 && (
{isResponseLoading(latestGrpcConnection) && (
<Icon spin size="sm" icon="update" className="text-fg-subtler" />
)}
</div>

View File

@@ -1,5 +1,5 @@
import classNames from 'classnames';
import { memo, useMemo } from 'react';
import { memo, useCallback, useMemo } from 'react';
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
import { useCreateWorkspace } from '../hooks/useCreateWorkspace';
import { useDeleteWorkspace } from '../hooks/useDeleteWorkspace';
@@ -8,12 +8,14 @@ import { usePrompt } from '../hooks/usePrompt';
import { useSettings } from '../hooks/useSettings';
import { useUpdateWorkspace } from '../hooks/useUpdateWorkspace';
import { useWorkspaces } from '../hooks/useWorkspaces';
import { getWorkspace } from '../lib/store';
import type { ButtonProps } from './core/Button';
import { Button } from './core/Button';
import type { DropdownItem } from './core/Dropdown';
import { Dropdown } from './core/Dropdown';
import { Icon } from './core/Icon';
import { InlineCode } from './core/InlineCode';
import type { RadioDropdownItem } from './core/RadioDropdown';
import { RadioDropdown } from './core/RadioDropdown';
import { useDialog } from './DialogContext';
import { OpenWorkspaceDialog } from './OpenWorkspaceDialog';
@@ -35,39 +37,18 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
const openWorkspace = useOpenWorkspace();
const openWorkspaceNewWindow = settings?.openWorkspaceNewWindow ?? null;
const items: DropdownItem[] = useMemo(() => {
const workspaceItems: DropdownItem[] = workspaces.map((w) => ({
const { workspaceItems, extraItems } = useMemo<{
workspaceItems: RadioDropdownItem[];
extraItems: DropdownItem[];
}>(() => {
const workspaceItems: RadioDropdownItem[] = workspaces.map((w) => ({
key: w.id,
label: w.name,
value: w.id,
leftSlot: w.id === activeWorkspaceId ? <Icon icon="check" /> : <Icon icon="empty" />,
onSelect: async () => {
if (typeof openWorkspaceNewWindow === 'boolean') {
openWorkspace.mutate({ workspace: w, inNewWindow: openWorkspaceNewWindow });
return;
}
dialog.show({
id: 'open-workspace',
size: 'sm',
title: 'Open Workspace',
render: ({ hide }) => <OpenWorkspaceDialog workspace={w} hide={hide} />,
});
},
}));
const activeWorkspaceItems: DropdownItem[] =
workspaces.length <= 1
? []
: [
...workspaceItems,
{
type: 'separator',
label: activeWorkspace?.name,
},
];
return [
...activeWorkspaceItems,
const extraItems: DropdownItem[] = [
{
key: 'rename',
label: 'Rename',
@@ -104,21 +85,47 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
onSelect: createWorkspace.mutate,
},
];
return { workspaceItems, extraItems };
}, [
activeWorkspace?.name,
activeWorkspaceId,
createWorkspace,
deleteWorkspace.mutate,
dialog,
openWorkspace,
prompt,
openWorkspaceNewWindow,
updateWorkspace,
workspaces,
]);
const handleChange = useCallback(
async (workspaceId: string | null) => {
if (workspaceId == null) return;
if (typeof openWorkspaceNewWindow === 'boolean') {
openWorkspace.mutate({ workspaceId, inNewWindow: openWorkspaceNewWindow });
return;
}
const workspace = await getWorkspace(workspaceId);
if (workspace == null) return;
dialog.show({
id: 'open-workspace',
size: 'sm',
title: 'Open Workspace',
render: ({ hide }) => <OpenWorkspaceDialog workspace={workspace} hide={hide} />,
});
},
[dialog, openWorkspace, openWorkspaceNewWindow],
);
return (
<Dropdown items={items}>
<RadioDropdown
items={workspaceItems}
extraItems={extraItems}
onChange={handleChange}
value={activeWorkspaceId}
>
<Button
size="sm"
className={classNames(
@@ -130,6 +137,6 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
>
{activeWorkspace?.name ?? 'Workspace'}
</Button>
</Dropdown>
</RadioDropdown>
);
});

View File

@@ -19,7 +19,8 @@
}
.cm-line {
@apply text-fg pl-1 pr-1.5;
@apply w-full; /* Important! Ensure it spans the entire width */
@apply w-full text-fg pl-1 pr-1.5;
}
.cm-placeholder {
@@ -207,7 +208,7 @@
/* NOTE: Extra selector required to override default styles */
.cm-tooltip.cm-tooltip-autocomplete,
.cm-tooltip.cm-completionInfo {
@apply shadow-lg bg-background rounded text-fg-subtle border border-background-highlight z-50 pointer-events-auto text-sm;
@apply shadow-lg bg-background rounded text-fg-subtle border border-background-highlight z-50 pointer-events-auto text-editor;
.cm-completionIcon {
@apply italic font-mono;
@@ -285,7 +286,7 @@
}
& > ul > li {
@apply cursor-default px-2 py-1.5 rounded-sm text-fg-subtle flex items-center;
@apply cursor-default px-2 h-[2em] rounded-sm text-fg flex items-center;
}
& > ul > li[aria-selected] {
@@ -297,11 +298,11 @@
}
.cm-completionLabel {
@apply text-fg-subtle;
@apply text-fg;
}
.cm-completionDetail {
@apply ml-auto pl-6;
@apply ml-auto pl-6 text-fg-subtle;
}
}
}

View File

@@ -242,6 +242,7 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
size="sm"
title="Reformat contents"
icon="magicWand"
variant="border"
className={classNames(actionClassName)}
onClick={() => {
if (cm.current === null) return;

View File

@@ -13,8 +13,8 @@ export function CsvViewer({ response, className }: Props) {
const body = useResponseBodyText(response);
const parsed = useMemo(() => {
if (body === null) return null;
return Papa.parse<string[]>(body);
if (body.data == null) return null;
return Papa.parse<string[]>(body.data);
}, [body]);
if (parsed === null) return null;

View File

@@ -9,12 +9,15 @@ interface Props {
}
export function JsonViewer({ response, className }: Props) {
const rawBody = useResponseBodyText(response) ?? '';
const rawBody = useResponseBodyText(response);
if (rawBody.isLoading || rawBody.data == null) return null;
let parsed = {};
try {
parsed = JSON.parse(rawBody);
parsed = JSON.parse(rawBody.data);
} catch (e) {
// foo
// Nothing yet
}
return (

View File

@@ -37,7 +37,7 @@ export function TextViewer({ response, pretty, className }: Props) {
);
const contentType = useContentTypeFromHeaders(response.headers);
const rawBody = useResponseBodyText(response) ?? null;
const rawBody = useResponseBodyText(response);
const isSearching = filterText != null;
const filteredResponse = useFilterResponse({
@@ -99,7 +99,11 @@ export function TextViewer({ response, pretty, className }: Props) {
return result;
}, [canFilter, filterText, isJson, isSearching, response.id, setFilterText, toggleSearch]);
if (rawBody == null) {
if (rawBody.isLoading) {
return null;
}
if (rawBody.data == null) {
return <BinaryViewer response={response} />;
}
@@ -109,10 +113,11 @@ export function TextViewer({ response, pretty, className }: Props) {
const formattedBody =
pretty && contentType?.includes('json')
? tryFormatJson(rawBody)
? tryFormatJson(rawBody.data)
: pretty && contentType?.includes('xml')
? tryFormatXml(rawBody)
: rawBody;
? tryFormatXml(rawBody.data)
: rawBody.data;
const body = isSearching && filterText?.length > 0 ? filteredResponse : formattedBody;
return (

View File

@@ -8,7 +8,7 @@ interface Props {
export function WebPageViewer({ response }: Props) {
const { url } = response;
const body = useResponseBodyText(response) ?? '';
const body = useResponseBodyText(response).data ?? '';
const contentForIframe: string | undefined = useMemo(() => {
if (body.includes('<head>')) {

View File

@@ -1,5 +1,5 @@
import { useQuery } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api/core';
import { invokeCmd } from '../lib/tauri';
export interface AppInfo {
isDev: boolean;
@@ -13,7 +13,7 @@ export function useAppInfo() {
return useQuery({
queryKey: ['appInfo'],
queryFn: async () => {
const metadata = await invoke('cmd_metadata');
const metadata = await invokeCmd('cmd_metadata');
return metadata as AppInfo;
},
}).data;

View File

@@ -1,7 +1,7 @@
import { useMutation } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api/core';
import { InlineCode } from '../components/core/InlineCode';
import { minPromiseMillis } from '../lib/minPromiseMillis';
import { invokeCmd } from '../lib/tauri';
import { useAlert } from './useAlert';
import { useAppInfo } from './useAppInfo';
@@ -10,7 +10,7 @@ export function useCheckForUpdates() {
const appInfo = useAppInfo();
return useMutation({
mutationFn: async () => {
const hasUpdate: boolean = await minPromiseMillis(invoke('cmd_check_for_updates'), 500);
const hasUpdate: boolean = await minPromiseMillis(invokeCmd('cmd_check_for_updates'), 500);
if (!hasUpdate) {
alert({
id: 'no-updates',

View File

@@ -1,6 +1,6 @@
import { useQuery } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api/core';
import type { CookieJar } from '../lib/models';
import { invokeCmd } from '../lib/tauri';
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
export function cookieJarsQueryKey({ workspaceId }: { workspaceId: string }) {
@@ -15,7 +15,7 @@ export function useCookieJars() {
queryKey: cookieJarsQueryKey({ workspaceId: workspaceId ?? 'n/a' }),
queryFn: async () => {
if (workspaceId == null) return [];
return (await invoke('cmd_list_cookie_jars', { workspaceId })) as CookieJar[];
return (await invokeCmd('cmd_list_cookie_jars', { workspaceId })) as CookieJar[];
},
}).data ?? []
);

View File

@@ -1,4 +1,4 @@
import { invoke } from '@tauri-apps/api/core';
import { invokeCmd } from '../lib/tauri';
import { useActiveEnvironmentId } from './useActiveEnvironmentId';
import { useClipboardText } from './useClipboardText';
@@ -6,7 +6,7 @@ export function useCopyAsCurl(requestId: string) {
const [, copy] = useClipboardText();
const environmentId = useActiveEnvironmentId();
return async () => {
const cmd: string = await invoke('cmd_request_to_curl', { requestId, environmentId });
const cmd: string = await invokeCmd('cmd_request_to_curl', { requestId, environmentId });
copy(cmd);
return cmd;
};

View File

@@ -1,7 +1,7 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api/core';
import { trackEvent } from '../lib/analytics';
import type { CookieJar } from '../lib/models';
import { invokeCmd } from '../lib/tauri';
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
import { cookieJarsQueryKey } from './useCookieJars';
import { usePrompt } from './usePrompt';
@@ -24,7 +24,7 @@ export function useCreateCookieJar() {
label: 'Name',
defaultValue: 'My Jar',
});
return invoke('cmd_create_cookie_jar', { workspaceId, name });
return invokeCmd('cmd_create_cookie_jar', { workspaceId, name });
},
onSettled: () => trackEvent('cookie_jar', 'create'),
onSuccess: async (cookieJar) => {

View File

@@ -1,7 +1,7 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api/core';
import { trackEvent } from '../lib/analytics';
import type { Environment } from '../lib/models';
import { invokeCmd } from '../lib/tauri';
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
import { useAppRoutes } from './useAppRoutes';
import { environmentsQueryKey } from './useEnvironments';
@@ -24,7 +24,7 @@ export function useCreateEnvironment() {
placeholder: 'My Environment',
defaultValue: 'My Environment',
});
return invoke('cmd_create_environment', { name, variables: [], workspaceId });
return invokeCmd('cmd_create_environment', { name, variables: [], workspaceId });
},
onSettled: () => trackEvent('environment', 'create'),
onSuccess: async (environment) => {

View File

@@ -1,7 +1,7 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api/core';
import { trackEvent } from '../lib/analytics';
import type { Folder } from '../lib/models';
import { invokeCmd } from '../lib/tauri';
import { useActiveRequest } from './useActiveRequest';
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
import { foldersQueryKey } from './useFolders';
@@ -31,7 +31,7 @@ export function useCreateFolder() {
}));
patch.sortPriority = patch.sortPriority || -Date.now();
patch.folderId = patch.folderId || activeRequest?.folderId;
return invoke('cmd_create_folder', { workspaceId, ...patch });
return invokeCmd('cmd_create_folder', { workspaceId, ...patch });
},
onSettled: () => trackEvent('folder', 'create'),
onSuccess: async (request) => {

View File

@@ -1,7 +1,7 @@
import { useMutation } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api/core';
import { trackEvent } from '../lib/analytics';
import type { GrpcRequest } from '../lib/models';
import { invokeCmd } from '../lib/tauri';
import { useActiveEnvironmentId } from './useActiveEnvironmentId';
import { useActiveRequest } from './useActiveRequest';
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
@@ -32,7 +32,7 @@ export function useCreateGrpcRequest() {
}
}
patch.folderId = patch.folderId || activeRequest?.folderId;
return invoke('cmd_create_grpc_request', { workspaceId, name: '', ...patch });
return invokeCmd('cmd_create_grpc_request', { workspaceId, name: '', ...patch });
},
onSettled: () => trackEvent('grpc_request', 'create'),
onSuccess: async (request) => {

View File

@@ -1,7 +1,7 @@
import { useMutation } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api/core';
import { trackEvent } from '../lib/analytics';
import type { HttpRequest } from '../lib/models';
import { invokeCmd } from '../lib/tauri';
import { useActiveEnvironmentId } from './useActiveEnvironmentId';
import { useActiveRequest } from './useActiveRequest';
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
@@ -28,7 +28,7 @@ export function useCreateHttpRequest() {
}
}
patch.folderId = patch.folderId || activeRequest?.folderId;
return invoke('cmd_create_http_request', { request: { workspaceId, ...patch } });
return invokeCmd('cmd_create_http_request', { request: { workspaceId, ...patch } });
},
onSettled: () => trackEvent('http_request', 'create'),
onSuccess: async (request) => {

View File

@@ -1,6 +1,6 @@
import { useMutation } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api/core';
import type { Workspace } from '../lib/models';
import { invokeCmd } from '../lib/tauri';
import { useAppRoutes } from './useAppRoutes';
import { usePrompt } from './usePrompt';
@@ -18,7 +18,7 @@ export function useCreateWorkspace() {
confirmLabel: 'Create',
placeholder: 'My Workspace',
});
return invoke('cmd_create_workspace', { name });
return invokeCmd('cmd_create_workspace', { name });
},
onSuccess: async (workspace) => {
routes.navigate('workspace', { workspaceId: workspace.id });

View File

@@ -1,10 +1,10 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api/core';
import { InlineCode } from '../components/core/InlineCode';
import { trackEvent } from '../lib/analytics';
import { fallbackRequestName } from '../lib/fallbackRequestName';
import type { GrpcRequest } from '../lib/models';
import { getGrpcRequest } from '../lib/store';
import { invokeCmd } from '../lib/tauri';
import { useConfirm } from './useConfirm';
import { grpcRequestsQueryKey } from './useGrpcRequests';
@@ -28,7 +28,7 @@ export function useDeleteAnyGrpcRequest() {
),
});
if (!confirmed) return null;
return invoke('cmd_delete_grpc_request', { requestId: id });
return invokeCmd('cmd_delete_grpc_request', { requestId: id });
},
onSettled: () => trackEvent('grpc_request', 'delete'),
onSuccess: async (request) => {

View File

@@ -1,10 +1,10 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api/core';
import { InlineCode } from '../components/core/InlineCode';
import { trackEvent } from '../lib/analytics';
import { fallbackRequestName } from '../lib/fallbackRequestName';
import type { HttpRequest } from '../lib/models';
import { getHttpRequest } from '../lib/store';
import { invokeCmd } from '../lib/tauri';
import { useConfirm } from './useConfirm';
import { httpRequestsQueryKey } from './useHttpRequests';
import { httpResponsesQueryKey } from './useHttpResponses';
@@ -29,7 +29,7 @@ export function useDeleteAnyHttpRequest() {
),
});
if (!confirmed) return null;
return invoke('cmd_delete_http_request', { requestId: id });
return invokeCmd('cmd_delete_http_request', { requestId: id });
},
onSettled: () => trackEvent('http_request', 'delete'),
onSuccess: async (request) => {

View File

@@ -1,8 +1,8 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api/core';
import { InlineCode } from '../components/core/InlineCode';
import { trackEvent } from '../lib/analytics';
import type { CookieJar } from '../lib/models';
import { invokeCmd } from '../lib/tauri';
import { useConfirm } from './useConfirm';
import { cookieJarsQueryKey } from './useCookieJars';
@@ -23,7 +23,7 @@ export function useDeleteCookieJar(cookieJar: CookieJar | null) {
),
});
if (!confirmed) return null;
return invoke('cmd_delete_cookie_jar', { cookieJarId: cookieJar?.id });
return invokeCmd('cmd_delete_cookie_jar', { cookieJarId: cookieJar?.id });
},
onSettled: () => trackEvent('cookie_jar', 'delete'),
onSuccess: async (cookieJar) => {

View File

@@ -1,8 +1,8 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api/core';
import { InlineCode } from '../components/core/InlineCode';
import { trackEvent } from '../lib/analytics';
import type { Environment, Workspace } from '../lib/models';
import { invokeCmd } from '../lib/tauri';
import { useConfirm } from './useConfirm';
import { environmentsQueryKey } from './useEnvironments';
@@ -23,7 +23,7 @@ export function useDeleteEnvironment(environment: Environment | null) {
),
});
if (!confirmed) return null;
return invoke('cmd_delete_environment', { environmentId: environment?.id });
return invokeCmd('cmd_delete_environment', { environmentId: environment?.id });
},
onSettled: () => trackEvent('environment', 'delete'),
onSuccess: async (environment) => {

View File

@@ -1,9 +1,9 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api/core';
import { InlineCode } from '../components/core/InlineCode';
import { trackEvent } from '../lib/analytics';
import type { Folder } from '../lib/models';
import { getFolder } from '../lib/store';
import { invokeCmd } from '../lib/tauri';
import { useConfirm } from './useConfirm';
import { foldersQueryKey } from './useFolders';
import { httpRequestsQueryKey } from './useHttpRequests';
@@ -26,7 +26,7 @@ export function useDeleteFolder(id: string | null) {
),
});
if (!confirmed) return null;
return invoke('cmd_delete_folder', { folderId: id });
return invokeCmd('cmd_delete_folder', { folderId: id });
},
onSettled: () => trackEvent('folder', 'delete'),
onSuccess: async (folder) => {

View File

@@ -1,14 +1,14 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api/core';
import { trackEvent } from '../lib/analytics';
import type { GrpcConnection } from '../lib/models';
import { invokeCmd } from '../lib/tauri';
import { grpcConnectionsQueryKey } from './useGrpcConnections';
export function useDeleteGrpcConnection(id: string | null) {
const queryClient = useQueryClient();
return useMutation<GrpcConnection>({
mutationFn: async () => {
return await invoke('cmd_delete_grpc_connection', { id: id });
return await invokeCmd('cmd_delete_grpc_connection', { id: id });
},
onSettled: () => trackEvent('grpc_connection', 'delete'),
onSuccess: ({ requestId, id: connectionId }) => {

View File

@@ -1,6 +1,6 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api/core';
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri';
import { grpcConnectionsQueryKey } from './useGrpcConnections';
export function useDeleteGrpcConnections(requestId?: string) {
@@ -8,7 +8,7 @@ export function useDeleteGrpcConnections(requestId?: string) {
return useMutation({
mutationFn: async () => {
if (requestId === undefined) return;
await invoke('cmd_delete_all_grpc_connections', { requestId });
await invokeCmd('cmd_delete_all_grpc_connections', { requestId });
},
onSettled: () => trackEvent('grpc_connection', 'delete_many'),
onSuccess: async () => {

View File

@@ -1,14 +1,14 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api/core';
import { trackEvent } from '../lib/analytics';
import type { HttpResponse } from '../lib/models';
import { invokeCmd } from '../lib/tauri';
import { httpResponsesQueryKey } from './useHttpResponses';
export function useDeleteHttpResponse(id: string | null) {
const queryClient = useQueryClient();
return useMutation<HttpResponse>({
mutationFn: async () => {
return await invoke('cmd_delete_http_response', { id: id });
return await invokeCmd('cmd_delete_http_response', { id: id });
},
onSettled: () => trackEvent('http_response', 'delete'),
onSuccess: ({ requestId, id: responseId }) => {

View File

@@ -1,6 +1,6 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api/core';
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri';
import { httpResponsesQueryKey } from './useHttpResponses';
export function useDeleteHttpResponses(requestId?: string) {
@@ -8,7 +8,7 @@ export function useDeleteHttpResponses(requestId?: string) {
return useMutation({
mutationFn: async () => {
if (requestId === undefined) return;
await invoke('cmd_delete_all_http_responses', { requestId });
await invokeCmd('cmd_delete_all_http_responses', { requestId });
},
onSettled: () => trackEvent('http_response', 'delete_many'),
onSuccess: async () => {

View File

@@ -1,8 +1,8 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api/core';
import { InlineCode } from '../components/core/InlineCode';
import { trackEvent } from '../lib/analytics';
import type { Workspace } from '../lib/models';
import { invokeCmd } from '../lib/tauri';
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
import { useAppRoutes } from './useAppRoutes';
import { useConfirm } from './useConfirm';
@@ -28,7 +28,7 @@ export function useDeleteWorkspace(workspace: Workspace | null) {
),
});
if (!confirmed) return null;
return invoke('cmd_delete_workspace', { workspaceId: workspace?.id });
return invokeCmd('cmd_delete_workspace', { workspaceId: workspace?.id });
},
onSettled: () => trackEvent('workspace', 'delete'),
onSuccess: async (workspace) => {

View File

@@ -1,8 +1,8 @@
import { useMutation } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api/core';
import { trackEvent } from '../lib/analytics';
import { setKeyValue } from '../lib/keyValueStore';
import type { GrpcRequest } from '../lib/models';
import { invokeCmd } from '../lib/tauri';
import { useActiveEnvironmentId } from './useActiveEnvironmentId';
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
import { useAppRoutes } from './useAppRoutes';
@@ -22,7 +22,7 @@ export function useDuplicateGrpcRequest({
return useMutation<GrpcRequest, string>({
mutationFn: async () => {
if (id === null) throw new Error("Can't duplicate a null grpc request");
return invoke('cmd_duplicate_grpc_request', { id });
return invokeCmd('cmd_duplicate_grpc_request', { id });
},
onSettled: () => trackEvent('grpc_request', 'duplicate'),
onSuccess: async (request) => {

View File

@@ -1,7 +1,7 @@
import { useMutation } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api/core';
import { trackEvent } from '../lib/analytics';
import type { HttpRequest } from '../lib/models';
import { invokeCmd } from '../lib/tauri';
import { useActiveEnvironmentId } from './useActiveEnvironmentId';
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
import { useAppRoutes } from './useAppRoutes';
@@ -19,7 +19,7 @@ export function useDuplicateHttpRequest({
return useMutation<HttpRequest, string>({
mutationFn: async () => {
if (id === null) throw new Error("Can't duplicate a null request");
return invoke('cmd_duplicate_http_request', { id });
return invokeCmd('cmd_duplicate_http_request', { id });
},
onSettled: () => trackEvent('http_request', 'duplicate'),
onSuccess: async (request) => {

View File

@@ -1,6 +1,6 @@
import { useQuery } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api/core';
import type { Environment } from '../lib/models';
import { invokeCmd } from '../lib/tauri';
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
export function environmentsQueryKey({ workspaceId }: { workspaceId: string }) {
@@ -15,7 +15,7 @@ export function useEnvironments() {
queryKey: environmentsQueryKey({ workspaceId: workspaceId ?? 'n/a' }),
queryFn: async () => {
if (workspaceId == null) return [];
return (await invoke('cmd_list_environments', { workspaceId })) as Environment[];
return (await invokeCmd('cmd_list_environments', { workspaceId })) as Environment[];
},
}).data ?? []
);

View File

@@ -1,5 +1,5 @@
import { useQuery } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api/core';
import { invokeCmd } from '../lib/tauri';
export function useFilterResponse({
responseId,
@@ -16,7 +16,7 @@ export function useFilterResponse({
return null;
}
return (await invoke('cmd_filter_response', { responseId, filter })) as string | null;
return (await invokeCmd('cmd_filter_response', { responseId, filter })) as string | null;
},
}).data ?? ''
);

View File

@@ -1,6 +1,6 @@
import { useQuery } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api/core';
import type { Folder } from '../lib/models';
import { invokeCmd } from '../lib/tauri';
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
export function foldersQueryKey({ workspaceId }: { workspaceId: string }) {
@@ -15,7 +15,7 @@ export function useFolders() {
queryKey: foldersQueryKey({ workspaceId: workspaceId ?? 'n/a' }),
queryFn: async () => {
if (workspaceId == null) return [];
return (await invoke('cmd_list_folders', { workspaceId })) as Folder[];
return (await invokeCmd('cmd_list_folders', { workspaceId })) as Folder[];
},
}).data ?? []
);

View File

@@ -1,9 +1,9 @@
import { useMutation, useQuery } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api/core';
import { emit } from '@tauri-apps/api/event';
import { trackEvent } from '../lib/analytics';
import { minPromiseMillis } from '../lib/minPromiseMillis';
import type { GrpcConnection, GrpcRequest } from '../lib/models';
import { invokeCmd } from '../lib/tauri';
import { useActiveEnvironmentId } from './useActiveEnvironmentId';
import { useDebouncedValue } from './useDebouncedValue';
@@ -21,7 +21,8 @@ export function useGrpc(
const environmentId = useActiveEnvironmentId();
const go = useMutation<void, string>({
mutationFn: async () => await invoke('cmd_grpc_go', { requestId, environmentId, protoFiles }),
mutationFn: async () =>
await invokeCmd('cmd_grpc_go', { requestId, environmentId, protoFiles }),
onSettled: () => trackEvent('grpc_request', 'send'),
});
@@ -43,25 +44,26 @@ export function useGrpc(
onSettled: () => trackEvent('grpc_connection', 'commit'),
});
const debouncedUrl = useDebouncedValue<string>(req?.url ?? 'n/a', 500);
const debouncedUrl = useDebouncedValue<string>(req?.url ?? '', 1000);
const debouncedMessage = useDebouncedValue<string>(req?.message ?? '', 1000);
const reflect = useQuery<ReflectResponseService[], string>({
enabled: req != null,
queryKey: ['grpc_reflect', req?.id ?? 'n/a', debouncedUrl, protoFiles],
refetchOnWindowFocus: false,
queryFn: async () => {
return (await minPromiseMillis(
invoke('cmd_grpc_reflect', { requestId, protoFiles }),
queryKey: ['grpc_reflect', req?.id ?? 'n/a', debouncedUrl, debouncedMessage, protoFiles],
queryFn: async () =>
(await minPromiseMillis(
invokeCmd('cmd_grpc_reflect', { requestId, protoFiles }),
300,
)) as ReflectResponseService[];
},
)) as ReflectResponseService[],
});
console.log('CONN', conn);
return {
go,
reflect,
cancel,
commit,
isStreaming: conn?.elapsed === 0,
isStreaming: conn != null && conn.elapsed === 0,
send,
};
}

View File

@@ -1,6 +1,6 @@
import { useQuery } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api/core';
import type { GrpcConnection } from '../lib/models';
import { invokeCmd } from '../lib/tauri';
export function grpcConnectionsQueryKey({ requestId }: { requestId: string }) {
return ['grpc_connections', { requestId }];
@@ -13,7 +13,7 @@ export function useGrpcConnections(requestId: string | null) {
initialData: [],
queryKey: grpcConnectionsQueryKey({ requestId: requestId ?? 'n/a' }),
queryFn: async () => {
return (await invoke('cmd_list_grpc_connections', {
return (await invokeCmd('cmd_list_grpc_connections', {
requestId,
limit: 200,
})) as GrpcConnection[];

View File

@@ -1,6 +1,6 @@
import { useQuery } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api/core';
import type { GrpcEvent } from '../lib/models';
import { invokeCmd } from '../lib/tauri';
export function grpcEventsQueryKey({ connectionId }: { connectionId: string }) {
return ['grpc_events', { connectionId }];
@@ -13,7 +13,7 @@ export function useGrpcEvents(connectionId: string | null) {
initialData: [],
queryKey: grpcEventsQueryKey({ connectionId: connectionId ?? 'n/a' }),
queryFn: async () => {
return (await invoke('cmd_list_grpc_events', {
return (await invokeCmd('cmd_list_grpc_events', {
connectionId,
limit: 200,
})) as GrpcEvent[];

View File

@@ -1,6 +1,6 @@
import { useQuery } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api/core';
import type { GrpcRequest } from '../lib/models';
import { invokeCmd } from '../lib/tauri';
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
export function grpcRequestsQueryKey({ workspaceId }: { workspaceId: string }) {
@@ -15,7 +15,7 @@ export function useGrpcRequests() {
queryKey: grpcRequestsQueryKey({ workspaceId: workspaceId ?? 'n/a' }),
queryFn: async () => {
if (workspaceId == null) return [];
return (await invoke('cmd_list_grpc_requests', { workspaceId })) as GrpcRequest[];
return (await invokeCmd('cmd_list_grpc_requests', { workspaceId })) as GrpcRequest[];
},
}).data ?? []
);

View File

@@ -1,6 +1,6 @@
import { useQuery } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api/core';
import type { HttpRequest } from '../lib/models';
import { invokeCmd } from '../lib/tauri';
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
export function httpRequestsQueryKey({ workspaceId }: { workspaceId: string }) {
@@ -15,7 +15,7 @@ export function useHttpRequests() {
queryKey: httpRequestsQueryKey({ workspaceId: workspaceId ?? 'n/a' }),
queryFn: async () => {
if (workspaceId == null) return [];
return (await invoke('cmd_list_http_requests', { workspaceId })) as HttpRequest[];
return (await invokeCmd('cmd_list_http_requests', { workspaceId })) as HttpRequest[];
},
}).data ?? []
);

View File

@@ -1,6 +1,6 @@
import { useQuery } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api/core';
import type { HttpResponse } from '../lib/models';
import { invokeCmd } from '../lib/tauri';
export function httpResponsesQueryKey({ requestId }: { requestId: string }) {
return ['http_responses', { requestId }];
@@ -13,7 +13,7 @@ export function useHttpResponses(requestId: string | null) {
initialData: [],
queryKey: httpResponsesQueryKey({ requestId: requestId ?? 'n/a' }),
queryFn: async () => {
return (await invoke('cmd_list_http_responses', {
return (await invokeCmd('cmd_list_http_responses', {
requestId,
limit: 200,
})) as HttpResponse[];

View File

@@ -1,5 +1,5 @@
import { invoke } from '@tauri-apps/api/core';
import { useMutation } from '@tanstack/react-query';
import { invokeCmd } from '../lib/tauri';
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
import { useRequestUpdateKey } from './useRequestUpdateKey';
import { useUpdateAnyHttpRequest } from './useUpdateAnyHttpRequest';
@@ -17,7 +17,7 @@ export function useImportCurl({ clearClipboard }: { clearClipboard?: boolean } =
return useMutation({
mutationFn: async ({ requestId, command }: { requestId: string | null; command: string }) => {
const request: Record<string, unknown> = await invoke('cmd_curl_to_request', {
const request: Record<string, unknown> = await invokeCmd('cmd_curl_to_request', {
command,
workspaceId,
});

View File

@@ -1,5 +1,4 @@
import { useMutation } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api/core';
import { open } from '@tauri-apps/plugin-dialog';
import { Button } from '../components/core/Button';
import { FormattedError } from '../components/core/FormattedError';
@@ -7,6 +6,7 @@ import { VStack } from '../components/core/Stacks';
import { useDialog } from '../components/DialogContext';
import type { Environment, Folder, GrpcRequest, HttpRequest, Workspace } from '../lib/models';
import { count } from '../lib/pluralize';
import { invokeCmd } from '../lib/tauri';
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
import { useAlert } from './useAlert';
import { useAppRoutes } from './useAppRoutes';
@@ -33,7 +33,7 @@ export function useImportData() {
folders: Folder[];
httpRequests: HttpRequest[];
grpcRequests: GrpcRequest[];
} = await invoke('cmd_import_data', {
} = await invokeCmd('cmd_import_data', {
filePath: selected.path,
workspaceId: activeWorkspaceId,
});

View File

@@ -1,6 +1,5 @@
import type { IntrospectionQuery } from 'graphql';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useLocalStorage } from 'react-use';
import { buildClientSchema, getIntrospectionQuery } from '../components/core/Editor';
import { minPromiseMillis } from '../lib/minPromiseMillis';
import type { HttpRequest } from '../lib/models';
@@ -8,6 +7,7 @@ import { getResponseBodyText } from '../lib/responseBody';
import { sendEphemeralRequest } from '../lib/sendEphemeralRequest';
import { useActiveEnvironmentId } from './useActiveEnvironmentId';
import { useDebouncedValue } from './useDebouncedValue';
import { useKeyValue } from './useKeyValue';
const introspectionRequestBody = JSON.stringify({
query: getIntrospectionQuery(),
@@ -22,9 +22,12 @@ export function useIntrospectGraphQL(baseRequest: HttpRequest) {
const [refetchKey, setRefetchKey] = useState<number>(0);
const [isLoading, setIsLoading] = useState<boolean>(false);
const [error, setError] = useState<string>();
const [introspection, setIntrospection] = useLocalStorage<IntrospectionQuery | null>(
`introspection:${baseRequest.id}`,
);
const { value: introspection, set: setIntrospection } = useKeyValue<IntrospectionQuery | null>({
key: ['graphql_introspection', baseRequest.id],
fallback: null,
namespace: 'global',
});
const introspectionInterval = useRef<NodeJS.Timeout>();
@@ -40,31 +43,26 @@ export function useIntrospectGraphQL(baseRequest: HttpRequest) {
const response = await minPromiseMillis(sendEphemeralRequest(args, activeEnvironmentId), 700);
if (response.error) {
return Promise.reject(new Error(response.error));
throw new Error(response.error);
}
const bodyText = await getResponseBodyText(response);
if (response.status < 200 || response.status >= 300) {
return Promise.reject(
new Error(`Request failed with status ${response.status}.\n\n${bodyText}`),
);
throw new Error(`Request failed with status ${response.status}.\n\n${bodyText}`);
}
if (bodyText === null) {
return Promise.reject(new Error('Empty body returned in response'));
throw new Error('Empty body returned in response');
}
const { data } = JSON.parse(bodyText);
console.log('Introspection response', data);
setIntrospection(data);
await setIntrospection(data);
};
const runIntrospection = () => {
fetchIntrospection()
.catch((e) => {
setIntrospection(null);
setError(e.message);
})
.catch((e) => setError(e.message))
.finally(() => setIsLoading(false));
};

View File

@@ -1,14 +1,14 @@
import { invoke } from '@tauri-apps/api/core';
import { open } from '@tauri-apps/plugin-shell';
import { Button } from '../components/core/Button';
import { useToast } from '../components/ToastContext';
import { invokeCmd } from '../lib/tauri';
import { useListenToTauriEvent } from './useListenToTauriEvent';
export function useNotificationToast() {
const toast = useToast();
const markRead = (id: string) => {
invoke('cmd_dismiss_notification', { notificationId: id }).catch(console.error);
invokeCmd('cmd_dismiss_notification', { notificationId: id }).catch(console.error);
};
useListenToTauriEvent<{

View File

@@ -1,6 +1,5 @@
import { useMutation } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api/core';
import type { Workspace } from '../lib/models';
import { invokeCmd } from '../lib/tauri';
import { useAppRoutes } from './useAppRoutes';
import { getRecentEnvironments } from './useRecentEnvironments';
import { getRecentRequests } from './useRecentRequests';
@@ -10,37 +9,35 @@ export function useOpenWorkspace() {
return useMutation({
mutationFn: async ({
workspace,
workspaceId,
inNewWindow,
}: {
workspace: Workspace;
workspaceId: string;
inNewWindow: boolean;
}) => {
if (workspace == null) return;
if (inNewWindow) {
const environmentId = (await getRecentEnvironments(workspace.id))[0];
const requestId = (await getRecentRequests(workspace.id))[0];
const environmentId = (await getRecentEnvironments(workspaceId))[0];
const requestId = (await getRecentRequests(workspaceId))[0];
const path =
requestId != null
? routes.paths.request({
workspaceId: workspace.id,
workspaceId,
environmentId,
requestId,
})
: routes.paths.workspace({ workspaceId: workspace.id, environmentId });
await invoke('cmd_new_window', { url: path });
: routes.paths.workspace({ workspaceId, environmentId });
await invokeCmd('cmd_new_window', { url: path });
} else {
const environmentId = (await getRecentEnvironments(workspace.id))[0];
const requestId = (await getRecentRequests(workspace.id))[0];
const environmentId = (await getRecentEnvironments(workspaceId))[0];
const requestId = (await getRecentRequests(workspaceId))[0];
if (requestId != null) {
routes.navigate('request', {
workspaceId: workspace.id,
workspaceId: workspaceId,
environmentId,
requestId,
});
} else {
routes.navigate('workspace', { workspaceId: workspace.id, environmentId });
routes.navigate('workspace', { workspaceId, environmentId });
}
}
},

View File

@@ -5,7 +5,6 @@ import { getResponseBodyText } from '../lib/responseBody';
export function useResponseBodyText(response: HttpResponse) {
return useQuery<string | null>({
queryKey: ['response-body-text', response?.updatedAt],
initialData: null,
queryFn: () => getResponseBodyText(response),
}).data;
});
}

View File

@@ -1,5 +1,4 @@
import { useMutation } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api/core';
import { save } from '@tauri-apps/plugin-dialog';
import mime from 'mime';
import slugify from 'slugify';
@@ -8,6 +7,7 @@ import { useToast } from '../components/ToastContext';
import type { HttpResponse } from '../lib/models';
import { getContentTypeHeader } from '../lib/models';
import { getHttpRequest } from '../lib/store';
import { invokeCmd } from '../lib/tauri';
export function useSaveResponse(response: HttpResponse) {
const toast = useToast();
@@ -24,7 +24,7 @@ export function useSaveResponse(response: HttpResponse) {
defaultPath: ext ? `${slug}.${ext}` : slug,
title: 'Save Response',
});
await invoke('cmd_save_response', { responseId: response.id, filepath });
await invokeCmd('cmd_save_response', { responseId: response.id, filepath });
toast.show({
message: (
<>

View File

@@ -1,10 +1,10 @@
import { useMutation } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api/core';
import { save } from '@tauri-apps/plugin-dialog';
import slugify from 'slugify';
import { trackEvent } from '../lib/analytics';
import type { HttpResponse } from '../lib/models';
import { getHttpRequest } from '../lib/store';
import { invokeCmd } from '../lib/tauri';
import { useActiveCookieJar } from './useActiveCookieJar';
import { useActiveEnvironment } from './useActiveEnvironment';
import { useAlert } from './useAlert';
@@ -31,7 +31,7 @@ export function useSendAnyRequest(options: { download?: boolean } = {}) {
}
}
return invoke('cmd_send_http_request', {
return invokeCmd('cmd_send_http_request', {
requestId: id,
environmentId: environment?.id,
downloadDir: downloadDir,

View File

@@ -1,6 +1,6 @@
import { useQuery } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api/core';
import type { Settings } from '../lib/models';
import { invokeCmd } from '../lib/tauri';
export function settingsQueryKey() {
return ['settings'];
@@ -11,7 +11,7 @@ export function useSettings() {
useQuery({
queryKey: settingsQueryKey(),
queryFn: async () => {
const settings = (await invoke('cmd_get_settings')) as Settings;
const settings = (await invokeCmd('cmd_get_settings')) as Settings;
return [settings];
},
}).data?.[0] ?? undefined

View File

@@ -1,7 +1,7 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api/core';
import type { Folder } from '../lib/models';
import { getFolder } from '../lib/store';
import { invokeCmd } from '../lib/tauri';
import { foldersQueryKey } from './useFolders';
export function useUpdateAnyFolder() {
@@ -14,7 +14,7 @@ export function useUpdateAnyFolder() {
throw new Error("Can't update a null folder");
}
await invoke('cmd_update_folder', { folder: update(folder) });
await invokeCmd('cmd_update_folder', { folder: update(folder) });
},
onMutate: async ({ id, update }) => {
const folder = await getFolder(id);

View File

@@ -1,7 +1,7 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api/core';
import type { GrpcRequest } from '../lib/models';
import { getGrpcRequest } from '../lib/store';
import { invokeCmd } from '../lib/tauri';
import { grpcRequestsQueryKey } from './useGrpcRequests';
export function useUpdateAnyGrpcRequest() {
@@ -20,7 +20,7 @@ export function useUpdateAnyGrpcRequest() {
const patchedRequest =
typeof update === 'function' ? update(request) : { ...request, ...update };
await invoke('cmd_update_grpc_request', { request: patchedRequest });
await invokeCmd('cmd_update_grpc_request', { request: patchedRequest });
},
onMutate: async ({ id, update }) => {
const request = await getGrpcRequest(id);

View File

@@ -1,7 +1,7 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api/core';
import type { HttpRequest } from '../lib/models';
import { getHttpRequest } from '../lib/store';
import { invokeCmd } from '../lib/tauri';
import { httpRequestsQueryKey } from './useHttpRequests';
export function useUpdateAnyHttpRequest() {
@@ -20,7 +20,7 @@ export function useUpdateAnyHttpRequest() {
const patchedRequest =
typeof update === 'function' ? update(request) : { ...request, ...update };
await invoke('cmd_update_http_request', { request: patchedRequest });
await invokeCmd('cmd_update_http_request', { request: patchedRequest });
},
onMutate: async ({ id, update }) => {
const request = await getHttpRequest(id);

View File

@@ -1,7 +1,7 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api/core';
import type { CookieJar } from '../lib/models';
import { getCookieJar } from '../lib/store';
import { invokeCmd } from '../lib/tauri';
import { cookieJarsQueryKey } from './useCookieJars';
export function useUpdateCookieJar(id: string | null) {
@@ -15,7 +15,7 @@ export function useUpdateCookieJar(id: string | null) {
const newCookieJar = typeof v === 'function' ? v(cookieJar) : { ...cookieJar, ...v };
console.log('NEW COOKIE JAR', newCookieJar.cookies.length);
await invoke('cmd_update_cookie_jar', { cookieJar: newCookieJar });
await invokeCmd('cmd_update_cookie_jar', { cookieJar: newCookieJar });
},
onMutate: async (v) => {
const cookieJar = await getCookieJar(id);

View File

@@ -1,7 +1,7 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api/core';
import type { Environment } from '../lib/models';
import { getEnvironment } from '../lib/store';
import { invokeCmd } from '../lib/tauri';
import { environmentsQueryKey } from './useEnvironments';
export function useUpdateEnvironment(id: string | null) {
@@ -14,7 +14,7 @@ export function useUpdateEnvironment(id: string | null) {
}
const newEnvironment = typeof v === 'function' ? v(environment) : { ...environment, ...v };
await invoke('cmd_update_environment', { environment: newEnvironment });
await invokeCmd('cmd_update_environment', { environment: newEnvironment });
},
onMutate: async (v) => {
const environment = await getEnvironment(id);

View File

@@ -1,6 +1,6 @@
import { useMutation } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api/core';
import type { Settings } from '../lib/models';
import { invokeCmd } from '../lib/tauri';
import { useSettings } from './useSettings';
export function useUpdateSettings() {
@@ -10,7 +10,7 @@ export function useUpdateSettings() {
mutationFn: async (patch) => {
if (settings == null) return;
const newSettings: Settings = { ...settings, ...patch };
await invoke('cmd_update_settings', { settings: newSettings });
await invokeCmd('cmd_update_settings', { settings: newSettings });
},
});
}

View File

@@ -1,7 +1,7 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api/core';
import type { Workspace } from '../lib/models';
import { getWorkspace } from '../lib/store';
import { invokeCmd } from '../lib/tauri';
import { workspacesQueryKey } from './useWorkspaces';
export function useUpdateWorkspace(id: string | null) {
@@ -14,7 +14,7 @@ export function useUpdateWorkspace(id: string | null) {
}
const newWorkspace = typeof v === 'function' ? v(workspace) : { ...workspace, ...v };
await invoke('cmd_update_workspace', { workspace: newWorkspace });
await invokeCmd('cmd_update_workspace', { workspace: newWorkspace });
},
onMutate: async (v) => {
const workspace = await getWorkspace(id);

View File

@@ -1,18 +0,0 @@
import { useQuery } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api/core';
import type { EnvironmentVariable } from '../lib/models';
export function variablesQueryKey({ environmentId }: { environmentId: string }) {
return ['variables', { environmentId }];
}
export function useVariables({ environmentId }: { environmentId: string }) {
return (
useQuery({
queryKey: variablesQueryKey({ environmentId }),
queryFn: async () => {
return (await invoke('cmd_list_variables', { environmentId })) as EnvironmentVariable[];
},
}).data ?? []
);
}

View File

@@ -1,6 +1,6 @@
import { useQuery } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api/core';
import type { Workspace } from '../lib/models';
import { invokeCmd } from '../lib/tauri';
// eslint-disable-next-line @typescript-eslint/no-unused-vars,@typescript-eslint/ban-types
export function workspacesQueryKey(_?: {}) {
@@ -12,7 +12,7 @@ export function useWorkspaces() {
useQuery({
queryKey: workspacesQueryKey(),
queryFn: async () => {
const workspaces = await invoke('cmd_list_workspaces');
const workspaces = await invokeCmd('cmd_list_workspaces');
return workspaces as Workspace[];
},
}).data ?? []

View File

@@ -1,4 +1,4 @@
import { invoke } from '@tauri-apps/api/core';
import { invokeCmd } from './tauri';
export type TrackResource =
| 'appearance'
@@ -37,7 +37,7 @@ export function trackEvent(
action: TrackAction,
attributes: Record<string, string | number> = {},
) {
invoke('cmd_track_event', {
invokeCmd('cmd_track_event', {
resource: resource,
action,
attributes,

View File

@@ -3,31 +3,25 @@ import type { GrpcRequest, HttpRequest } from './models';
export function fallbackRequestName(r: HttpRequest | GrpcRequest | null): string {
if (r == null) return '';
// Return name if it has one
if (r.name) {
return r.name;
}
const withoutVariables = r.url.replace(/\$\{\[\s*([^\]]+)\s*]}/g, '$1');
// Replace variable syntax with variable name
const withoutVariables = r.url.replace(/\$\{\[\s*([^\]\s]+)\s*]}/g, '$1');
if (withoutVariables.trim() === '') {
return r.model === 'http_request' ? 'New HTTP Request' : 'new gRPC Request';
}
const fixedUrl = withoutVariables.match(/^https?:\/\//)
? withoutVariables
: 'http://' + withoutVariables;
// GRPC gets nice short names
if (r.model === 'grpc_request' && r.service != null && r.method != null) {
const shortService = r.service.split('.').pop();
return `${shortService}/${r.method}`;
} else {
try {
const url = new URL(fixedUrl);
const pathname = url.pathname === '/' ? '' : url.pathname;
return `${url.host}${pathname}`;
} catch (_) {
// Nothing
}
}
return r.url;
// Strip unnecessary protocol
const withoutProto = withoutVariables.replace(/^https?:\/\//, '');
return withoutProto;
}

View File

@@ -1,5 +1,5 @@
import { invoke } from '@tauri-apps/api/core';
import type { KeyValue } from './models';
import { invokeCmd } from './tauri';
export async function setKeyValue<T>({
namespace = 'global',
@@ -10,7 +10,7 @@ export async function setKeyValue<T>({
key: string | string[];
value: T;
}): Promise<void> {
await invoke('cmd_set_key_value', {
await invokeCmd('cmd_set_key_value', {
namespace,
key: buildKeyValueKey(key),
value: JSON.stringify(value),
@@ -26,7 +26,7 @@ export async function getKeyValue<T>({
key: string | string[];
fallback: T;
}) {
const kv = (await invoke('cmd_get_key_value', {
const kv = (await invokeCmd('cmd_get_key_value', {
namespace,
key: buildKeyValueKey(key),
})) as KeyValue | null;

View File

@@ -207,7 +207,7 @@ export interface HttpResponse extends BaseModel {
readonly headers: HttpHeader[];
}
export function isResponseLoading(response: HttpResponse): boolean {
export function isResponseLoading(response: HttpResponse | GrpcConnection): boolean {
return response.elapsed === 0;
}

View File

@@ -1,5 +1,5 @@
import { invoke } from '@tauri-apps/api/core';
import type { HttpRequest, HttpResponse } from './models';
import { invokeCmd } from './tauri';
export async function sendEphemeralRequest(
request: HttpRequest,
@@ -7,5 +7,5 @@ export async function sendEphemeralRequest(
): Promise<HttpResponse> {
// Remove some things that we don't want to associate
const newRequest = { ...request };
return invoke('cmd_send_ephemeral_request', { request: newRequest, environmentId });
return invokeCmd('cmd_send_ephemeral_request', { request: newRequest, environmentId });
}

View File

@@ -1,4 +1,3 @@
import { invoke } from '@tauri-apps/api/core';
import type {
CookieJar,
Environment,
@@ -8,14 +7,15 @@ import type {
Settings,
Workspace,
} from './models';
import { invokeCmd } from './tauri';
export async function getSettings(): Promise<Settings> {
return invoke('cmd_get_settings', {});
return invokeCmd('cmd_get_settings', {});
}
export async function getGrpcRequest(id: string | null): Promise<GrpcRequest | null> {
if (id === null) return null;
const request: GrpcRequest = (await invoke('cmd_get_grpc_request', { id })) ?? null;
const request: GrpcRequest = (await invokeCmd('cmd_get_grpc_request', { id })) ?? null;
if (request == null) {
return null;
}
@@ -24,7 +24,7 @@ export async function getGrpcRequest(id: string | null): Promise<GrpcRequest | n
export async function getHttpRequest(id: string | null): Promise<HttpRequest | null> {
if (id === null) return null;
const request: HttpRequest = (await invoke('cmd_get_http_request', { id })) ?? null;
const request: HttpRequest = (await invokeCmd('cmd_get_http_request', { id })) ?? null;
if (request == null) {
return null;
}
@@ -33,7 +33,7 @@ export async function getHttpRequest(id: string | null): Promise<HttpRequest | n
export async function getEnvironment(id: string | null): Promise<Environment | null> {
if (id === null) return null;
const environment: Environment = (await invoke('cmd_get_environment', { id })) ?? null;
const environment: Environment = (await invokeCmd('cmd_get_environment', { id })) ?? null;
if (environment == null) {
return null;
}
@@ -42,7 +42,7 @@ export async function getEnvironment(id: string | null): Promise<Environment | n
export async function getFolder(id: string | null): Promise<Folder | null> {
if (id === null) return null;
const folder: Folder = (await invoke('cmd_get_folder', { id })) ?? null;
const folder: Folder = (await invokeCmd('cmd_get_folder', { id })) ?? null;
if (folder == null) {
return null;
}
@@ -51,7 +51,7 @@ export async function getFolder(id: string | null): Promise<Folder | null> {
export async function getWorkspace(id: string | null): Promise<Workspace | null> {
if (id === null) return null;
const workspace: Workspace = (await invoke('cmd_get_workspace', { id })) ?? null;
const workspace: Workspace = (await invokeCmd('cmd_get_workspace', { id })) ?? null;
if (workspace == null) {
return null;
}
@@ -60,7 +60,7 @@ export async function getWorkspace(id: string | null): Promise<Workspace | null>
export async function getCookieJar(id: string | null): Promise<CookieJar | null> {
if (id === null) return null;
const cookieJar: CookieJar = (await invoke('cmd_get_cookie_jar', { id })) ?? null;
const cookieJar: CookieJar = (await invokeCmd('cmd_get_cookie_jar', { id })) ?? null;
if (cookieJar == null) {
return null;
}

70
src-web/lib/tauri.ts Normal file
View File

@@ -0,0 +1,70 @@
import type { InvokeArgs } from '@tauri-apps/api/core';
import { invoke } from '@tauri-apps/api/core';
type TauriCmd =
| 'cmd_check_for_updates'
| 'cmd_create_cookie_jar'
| 'cmd_create_environment'
| 'cmd_create_folder'
| 'cmd_create_grpc_request'
| 'cmd_create_http_request'
| 'cmd_create_workspace'
| 'cmd_curl_to_request'
| 'cmd_delete_all_grpc_connections'
| 'cmd_delete_all_http_responses'
| 'cmd_delete_cookie_jar'
| 'cmd_delete_environment'
| 'cmd_delete_folder'
| 'cmd_delete_grpc_connection'
| 'cmd_delete_grpc_request'
| 'cmd_delete_http_request'
| 'cmd_delete_http_response'
| 'cmd_delete_workspace'
| 'cmd_duplicate_grpc_request'
| 'cmd_duplicate_http_request'
| 'cmd_export_data'
| 'cmd_filter_response'
| 'cmd_get_cookie_jar'
| 'cmd_get_environment'
| 'cmd_get_folder'
| 'cmd_get_grpc_request'
| 'cmd_get_http_request'
| 'cmd_get_key_value'
| 'cmd_get_settings'
| 'cmd_get_workspace'
| 'cmd_grpc_go'
| 'cmd_grpc_reflect'
| 'cmd_import_data'
| 'cmd_list_cookie_jars'
| 'cmd_list_environments'
| 'cmd_list_folders'
| 'cmd_list_grpc_connections'
| 'cmd_list_grpc_events'
| 'cmd_list_grpc_requests'
| 'cmd_list_http_requests'
| 'cmd_list_http_responses'
| 'cmd_list_workspaces'
| 'cmd_metadata'
| 'cmd_new_nested_window'
| 'cmd_new_window'
| 'cmd_request_to_curl'
| 'cmd_dismiss_notification'
| 'cmd_save_response'
| 'cmd_send_ephemeral_request'
| 'cmd_send_http_request'
| 'cmd_set_key_value'
| 'cmd_set_update_mode'
| 'cmd_track_event'
| 'cmd_update_cookie_jar'
| 'cmd_update_environment'
| 'cmd_update_folder'
| 'cmd_update_grpc_request'
| 'cmd_update_http_request'
| 'cmd_update_settings'
| 'cmd_update_workspace'
| 'cmd_write_file_dev';
export async function invokeCmd<T>(cmd: TauriCmd, args?: InvokeArgs): Promise<T> {
// console.log('RUN COMMAND', cmd, args);
return invoke(cmd, args);
}

View File

@@ -2,10 +2,10 @@ import { getCurrent } from '@tauri-apps/api/webviewWindow';
import { type } from '@tauri-apps/plugin-os';
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { pdfjs } from 'react-pdf';
import { attachConsole } from 'tauri-plugin-log-api';
import { App } from './components/App';
import './main.css';
import { pdfjs } from 'react-pdf';
pdfjs.GlobalWorkerOptions.workerSrc = new URL(
'pdfjs-dist/build/pdf.worker.min.mjs',