mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-08-16 08:31:57 +02:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4122b9d72a | ||
|
|
e294e6bcef |
Generated
-18
@@ -11106,7 +11106,6 @@ dependencies = [
|
||||
"yaak-models",
|
||||
"yaak-plugins",
|
||||
"yaak-rpc",
|
||||
"yaak-rpc-schema",
|
||||
"yaak-sse",
|
||||
"yaak-sync",
|
||||
"yaak-system-appearance",
|
||||
@@ -11444,22 +11443,6 @@ dependencies = [
|
||||
"ts-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yaak-rpc-schema"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"ts-rs",
|
||||
"yaak-git",
|
||||
"yaak-grpc",
|
||||
"yaak-models",
|
||||
"yaak-plugins",
|
||||
"yaak-sse",
|
||||
"yaak-sync",
|
||||
"yaak-templates",
|
||||
"yaak-ws",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yaak-server"
|
||||
version = "0.1.0"
|
||||
@@ -11490,7 +11473,6 @@ dependencies = [
|
||||
"yaak-models",
|
||||
"yaak-plugins",
|
||||
"yaak-rpc",
|
||||
"yaak-rpc-schema",
|
||||
"yaak-sse",
|
||||
"yaak-templates",
|
||||
]
|
||||
|
||||
@@ -5,7 +5,6 @@ members = [
|
||||
# Common/foundation crates
|
||||
"crates/common/yaak-database",
|
||||
"crates/common/yaak-rpc",
|
||||
"crates/common/yaak-rpc-schema",
|
||||
# Shared crates (no Tauri dependency)
|
||||
"crates/yaak-core",
|
||||
"crates/yaak-common",
|
||||
@@ -66,7 +65,6 @@ ts-rs = "11.1.0"
|
||||
# Internal crates - common/foundation
|
||||
yaak-database = { path = "crates/common/yaak-database" }
|
||||
yaak-rpc = { path = "crates/common/yaak-rpc" }
|
||||
yaak-rpc-schema = { path = "crates/common/yaak-rpc-schema" }
|
||||
|
||||
# Internal crates - shared
|
||||
yaak-core = { path = "crates/yaak-core" }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { RpcPayload } from "@yaakapp-internal/platform";
|
||||
import { platform } from "@yaakapp-internal/platform";
|
||||
import type { RpcSchema } from "@yaakapp-internal/rpc-schema";
|
||||
import type { RpcSchema } from "@yaakapp-internal/tauri-client";
|
||||
|
||||
/**
|
||||
* Every backend command the app can call: the generated wire schema, one field
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { HttpRequest } from "@yaakapp-internal/models";
|
||||
import type { EphemeralHttpResponse } from "@yaakapp-internal/rpc-schema";
|
||||
import type { EphemeralHttpResponse } from "@yaakapp-internal/tauri-client";
|
||||
import { getActiveCookieJar } from "../hooks/useActiveCookieJar";
|
||||
import { rpc } from "./rpc";
|
||||
|
||||
|
||||
@@ -43,6 +43,5 @@ yaak-http = { workspace = true }
|
||||
yaak-models = { workspace = true }
|
||||
yaak-plugins = { workspace = true }
|
||||
yaak-rpc = { workspace = true }
|
||||
yaak-rpc-schema = { workspace = true }
|
||||
yaak-sse = { workspace = true }
|
||||
yaak-templates = { workspace = true }
|
||||
|
||||
@@ -7,14 +7,15 @@
|
||||
//! they are plain data. The command *bodies* are what matter, and they call the
|
||||
//! same engine functions the desktop calls.
|
||||
|
||||
use super::{BridgeCtx, unsupported_command};
|
||||
use super::{BridgeCtx, UNSUPPORTED_COMMANDS, unsupported_command};
|
||||
use mime_guess::{Mime, mime};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
use yaak::import::{ImportDataParams, import_data as import_data_shared};
|
||||
use yaak::models_ops::{delete_model, duplicate_model, upsert_model};
|
||||
use yaak::send::{ResponseBody, SendHttpRequestWithPluginsParams, send_http_request_with_plugins};
|
||||
use yaak::send::{SendHttpRequestWithPluginsParams, send_http_request_with_plugins};
|
||||
use yaak_core::WorkspaceContext;
|
||||
use yaak_models::models::{
|
||||
AnyModel, Environment, GraphQlIntrospection, GrpcEvent, HttpRequest, HttpRequestHeader,
|
||||
@@ -35,11 +36,10 @@ use yaak_plugins::native_template_functions::{
|
||||
};
|
||||
use yaak_plugins::plugin_meta::PluginMetadata;
|
||||
use yaak_rpc::{RpcError, RpcRouter, rpc_handler_async};
|
||||
use yaak_rpc_schema::*;
|
||||
use yaak_sse::sse::ServerSentEvent;
|
||||
use yaak_templates::format_json::format_json;
|
||||
use yaak_templates::{
|
||||
RenderErrorBehavior, RenderOptions, TemplateCallback, parse_and_render,
|
||||
RenderErrorBehavior, RenderOptions, TemplateCallback, Tokens, parse_and_render,
|
||||
render_json_value_raw,
|
||||
};
|
||||
|
||||
@@ -68,11 +68,51 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! rpc_commands {
|
||||
( $( $name:ident ),* $(,)? ) => {
|
||||
pub fn build_router() -> RpcRouter<BridgeCtx> {
|
||||
let mut router = RpcRouter::new();
|
||||
$( router.register(stringify!($name), rpc_handler_async!($name)); )*
|
||||
for cmd in UNSUPPORTED_COMMANDS {
|
||||
router.register(
|
||||
cmd,
|
||||
Box::new(move |_ctx, _payload| {
|
||||
let cmd = *cmd;
|
||||
Box::pin(async move { Err(unsupported_command(cmd)) })
|
||||
}),
|
||||
);
|
||||
}
|
||||
router
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// -- App metadata --
|
||||
|
||||
async fn cmd_metadata(ctx: BridgeCtx, _req: CmdMetadataReq) -> Result<AppMetaData> {
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct EmptyReq {}
|
||||
|
||||
/// Deliberately not the desktop's `AppMetaData`: that type lives in a Tauri
|
||||
/// crate and half its fields are Tauri paths. The serialized shape is the same,
|
||||
/// which is what the frontend reads.
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BridgeMetaData {
|
||||
is_dev: bool,
|
||||
version: String,
|
||||
cli_version: Option<String>,
|
||||
name: String,
|
||||
app_data_dir: String,
|
||||
app_log_dir: String,
|
||||
vendored_plugin_dir: String,
|
||||
default_project_dir: String,
|
||||
feature_updater: bool,
|
||||
feature_license: bool,
|
||||
}
|
||||
|
||||
async fn cmd_metadata(ctx: BridgeCtx, _req: EmptyReq) -> Result<BridgeMetaData> {
|
||||
let data_dir = ctx.state.data_dir().to_string_lossy().to_string();
|
||||
Ok(AppMetaData {
|
||||
Ok(BridgeMetaData {
|
||||
is_dev: ctx.state.is_dev,
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
cli_version: None,
|
||||
@@ -97,11 +137,23 @@ async fn cmd_metadata(ctx: BridgeCtx, _req: CmdMetadataReq) -> Result<AppMetaDat
|
||||
|
||||
// -- Models --
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ModelsUpsertReq {
|
||||
pub model: AnyModel,
|
||||
}
|
||||
|
||||
async fn models_upsert(ctx: BridgeCtx, req: ModelsUpsertReq) -> Result<String> {
|
||||
let db = ctx.state.db();
|
||||
upsert_model(&db, ctx.state.blob_manager(), req.model, &ctx.update_source()).map_err(err)
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ModelsDeleteReq {
|
||||
pub model: AnyModel,
|
||||
}
|
||||
|
||||
/// Deletes run on a blocking thread, as they do on the desktop: a transaction
|
||||
/// holds a raw sqlite connection, which is neither `Send` nor cheap to hold —
|
||||
/// dropping a workspace with thousands of requests would otherwise stall the
|
||||
@@ -116,6 +168,13 @@ async fn models_delete(ctx: BridgeCtx, req: ModelsDeleteReq) -> Result<String> {
|
||||
.await
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ModelsDuplicateReq {
|
||||
pub model_type: String,
|
||||
pub model_id: String,
|
||||
}
|
||||
|
||||
async fn models_duplicate(ctx: BridgeCtx, req: ModelsDuplicateReq) -> Result<String> {
|
||||
let source = ctx.update_source();
|
||||
blocking(move || {
|
||||
@@ -126,10 +185,16 @@ async fn models_duplicate(ctx: BridgeCtx, req: ModelsDuplicateReq) -> Result<Str
|
||||
.await
|
||||
}
|
||||
|
||||
async fn models_get_settings(ctx: BridgeCtx, _req: ModelsGetSettingsReq) -> Result<Settings> {
|
||||
async fn models_get_settings(ctx: BridgeCtx, _req: EmptyReq) -> Result<Settings> {
|
||||
Ok(ctx.state.db().get_settings())
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ModelsWorkspaceModelsReq {
|
||||
pub workspace_id: Option<String>,
|
||||
}
|
||||
|
||||
/// Everything the frontend's model store needs for a workspace, as one JSON
|
||||
/// string.
|
||||
///
|
||||
@@ -203,6 +268,12 @@ async fn models_workspace_models(ctx: BridgeCtx, req: ModelsWorkspaceModelsReq)
|
||||
serde_json::to_string(&l).map_err(err)
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ModelsWebsocketEventsReq {
|
||||
pub connection_id: String,
|
||||
}
|
||||
|
||||
async fn models_websocket_events(
|
||||
ctx: BridgeCtx,
|
||||
req: ModelsWebsocketEventsReq,
|
||||
@@ -210,10 +281,22 @@ async fn models_websocket_events(
|
||||
ctx.state.db().list_websocket_events(&req.connection_id).map_err(err)
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ModelsGrpcEventsReq {
|
||||
pub connection_id: String,
|
||||
}
|
||||
|
||||
async fn models_grpc_events(ctx: BridgeCtx, req: ModelsGrpcEventsReq) -> Result<Vec<GrpcEvent>> {
|
||||
ctx.state.db().list_grpc_events(&req.connection_id).map_err(err)
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ModelsGetGraphqlIntrospectionReq {
|
||||
pub request_id: String,
|
||||
}
|
||||
|
||||
async fn models_get_graphql_introspection(
|
||||
ctx: BridgeCtx,
|
||||
req: ModelsGetGraphqlIntrospectionReq,
|
||||
@@ -221,6 +304,14 @@ async fn models_get_graphql_introspection(
|
||||
Ok(ctx.state.db().get_graphql_introspection(&req.request_id))
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ModelsUpsertGraphqlIntrospectionReq {
|
||||
pub request_id: String,
|
||||
pub workspace_id: String,
|
||||
pub content: Option<String>,
|
||||
}
|
||||
|
||||
async fn models_upsert_graphql_introspection(
|
||||
ctx: BridgeCtx,
|
||||
req: ModelsUpsertGraphqlIntrospectionReq,
|
||||
@@ -236,6 +327,12 @@ async fn models_upsert_graphql_introspection(
|
||||
.map_err(err)
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CmdGetWorkspaceMetaReq {
|
||||
pub workspace_id: String,
|
||||
}
|
||||
|
||||
async fn cmd_get_workspace_meta(
|
||||
ctx: BridgeCtx,
|
||||
req: CmdGetWorkspaceMetaReq,
|
||||
@@ -247,6 +344,14 @@ async fn cmd_get_workspace_meta(
|
||||
|
||||
// -- Sending --
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CmdSendHttpRequestReq {
|
||||
pub environment_id: Option<String>,
|
||||
pub cookie_jar_id: Option<String>,
|
||||
pub request_id: String,
|
||||
}
|
||||
|
||||
/// Send a saved request.
|
||||
///
|
||||
/// Same sequence as the desktop (crates-tauri/.../lib.rs `cmd_send_http_request`):
|
||||
@@ -338,13 +443,20 @@ async fn send_persisted(
|
||||
Ok(result.response)
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CmdSendEphemeralRequestReq {
|
||||
pub request: HttpRequest,
|
||||
pub environment_id: Option<String>,
|
||||
pub cookie_jar_id: Option<String>,
|
||||
}
|
||||
|
||||
/// Send without saving. An empty request id keeps the engine from persisting
|
||||
/// anything, so the body comes back in memory and rides along with the
|
||||
/// response — there is no row to look up later and no file to serve.
|
||||
/// anything, so the body comes back in memory instead of on disk.
|
||||
async fn cmd_send_ephemeral_request(
|
||||
ctx: BridgeCtx,
|
||||
req: CmdSendEphemeralRequestReq,
|
||||
) -> Result<EphemeralHttpResponse> {
|
||||
) -> Result<HttpResponse> {
|
||||
let mut request = req.request;
|
||||
request.id = String::new();
|
||||
let plugin_manager = ctx.plugins()?;
|
||||
@@ -370,18 +482,18 @@ async fn cmd_send_ephemeral_request(
|
||||
.await
|
||||
.map_err(err)?;
|
||||
|
||||
// Blanking the request id above is what makes this send unsaved, so the
|
||||
// engine always hands the body back. Failing loudly beats returning an
|
||||
// empty body that reads as "the server sent nothing".
|
||||
let ResponseBody::Returned(body) = result.response_body else {
|
||||
return Err(RpcError { message: "Unsaved response did not return a body".to_string() });
|
||||
};
|
||||
|
||||
Ok(EphemeralHttpResponse { response: result.response, body })
|
||||
Ok(result.response)
|
||||
}
|
||||
|
||||
// -- Reading responses --
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CmdHttpResponseBodyReq {
|
||||
pub response_id: String,
|
||||
pub filter: Option<String>,
|
||||
}
|
||||
|
||||
/// The frontend hands back an id and never a path, so the only bodies reachable
|
||||
/// here are ones the engine wrote and the database still knows about.
|
||||
async fn cmd_http_response_body(
|
||||
@@ -408,6 +520,12 @@ async fn cmd_http_response_body(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CmdHttpResponseBodyPathReq {
|
||||
pub response_id: String,
|
||||
}
|
||||
|
||||
/// The desktop host uses this to open the file itself. A tab cannot open a
|
||||
/// path, so the bridge's browser host never calls it — it fetches
|
||||
/// `/responses/:id/body` instead — but the command answers honestly for any
|
||||
@@ -437,6 +555,12 @@ fn parse_charset(content_type: &str) -> Option<String> {
|
||||
mime.get_param(mime::CHARSET).map(|v| v.to_string())
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CmdHttpRequestBodyReq {
|
||||
pub response_id: String,
|
||||
}
|
||||
|
||||
async fn cmd_http_request_body(
|
||||
ctx: BridgeCtx,
|
||||
req: CmdHttpRequestBodyReq,
|
||||
@@ -449,6 +573,12 @@ async fn cmd_http_request_body(
|
||||
Ok(Some(chunks.into_iter().flat_map(|c| c.data).collect()))
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CmdGetHttpResponseEventsReq {
|
||||
pub response_id: String,
|
||||
}
|
||||
|
||||
async fn cmd_get_http_response_events(
|
||||
ctx: BridgeCtx,
|
||||
req: CmdGetHttpResponseEventsReq,
|
||||
@@ -456,6 +586,12 @@ async fn cmd_get_http_response_events(
|
||||
ctx.state.db().list_http_response_events(&req.response_id).map_err(err)
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CmdGetSseEventsReq {
|
||||
pub response_id: String,
|
||||
}
|
||||
|
||||
async fn cmd_get_sse_events(
|
||||
ctx: BridgeCtx,
|
||||
req: CmdGetSseEventsReq,
|
||||
@@ -485,6 +621,12 @@ async fn cmd_get_sse_events(
|
||||
Ok(events)
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CmdDeleteAllHttpResponsesReq {
|
||||
pub request_id: String,
|
||||
}
|
||||
|
||||
async fn cmd_delete_all_http_responses(
|
||||
ctx: BridgeCtx,
|
||||
req: CmdDeleteAllHttpResponsesReq,
|
||||
@@ -496,6 +638,12 @@ async fn cmd_delete_all_http_responses(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CmdDeleteSendHistoryReq {
|
||||
pub workspace_id: String,
|
||||
}
|
||||
|
||||
async fn cmd_delete_send_history(ctx: BridgeCtx, req: CmdDeleteSendHistoryReq) -> Result<()> {
|
||||
let source = ctx.update_source();
|
||||
blocking(move || {
|
||||
@@ -511,10 +659,22 @@ async fn cmd_delete_send_history(ctx: BridgeCtx, req: CmdDeleteSendHistoryReq) -
|
||||
|
||||
// -- Formatting and templates --
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CmdFormatJsonReq {
|
||||
pub text: String,
|
||||
}
|
||||
|
||||
async fn cmd_format_json(_ctx: BridgeCtx, req: CmdFormatJsonReq) -> Result<String> {
|
||||
Ok(format_json(&req.text, " "))
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CmdFormatGraphqlReq {
|
||||
pub text: String,
|
||||
}
|
||||
|
||||
async fn cmd_format_graphql(_ctx: BridgeCtx, req: CmdFormatGraphqlReq) -> Result<String> {
|
||||
match pretty_graphql::format_text(&req.text, &Default::default()) {
|
||||
Ok(formatted) => Ok(formatted),
|
||||
@@ -522,6 +682,16 @@ async fn cmd_format_graphql(_ctx: BridgeCtx, req: CmdFormatGraphqlReq) -> Result
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CmdRenderTemplateReq {
|
||||
pub template: String,
|
||||
pub workspace_id: String,
|
||||
pub environment_id: Option<String>,
|
||||
pub purpose: Option<RenderPurpose>,
|
||||
pub ignore_error: Option<bool>,
|
||||
}
|
||||
|
||||
async fn cmd_render_template(ctx: BridgeCtx, req: CmdRenderTemplateReq) -> Result<String> {
|
||||
let environment_chain = ctx
|
||||
.state
|
||||
@@ -556,6 +726,12 @@ async fn render_json_value<T: TemplateCallback>(
|
||||
render_json_value_raw(value, vars, cb, opt).await
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CmdTemplateTokensToStringReq {
|
||||
pub tokens: Tokens,
|
||||
}
|
||||
|
||||
async fn cmd_template_tokens_to_string(
|
||||
_ctx: BridgeCtx,
|
||||
req: CmdTemplateTokensToStringReq,
|
||||
@@ -563,6 +739,12 @@ async fn cmd_template_tokens_to_string(
|
||||
Ok(req.tokens.to_string())
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CmdDecryptTemplateReq {
|
||||
pub template: String,
|
||||
}
|
||||
|
||||
async fn cmd_decrypt_template(ctx: BridgeCtx, req: CmdDecryptTemplateReq) -> Result<String> {
|
||||
decrypt_secure_template_function(
|
||||
&ctx.state.encryption_manager,
|
||||
@@ -572,6 +754,12 @@ async fn cmd_decrypt_template(ctx: BridgeCtx, req: CmdDecryptTemplateReq) -> Res
|
||||
.map_err(err)
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CmdSecureTemplateReq {
|
||||
pub template: String,
|
||||
}
|
||||
|
||||
async fn cmd_secure_template(ctx: BridgeCtx, req: CmdSecureTemplateReq) -> Result<String> {
|
||||
encrypt_secure_template_function(
|
||||
ctx.plugins()?,
|
||||
@@ -582,13 +770,13 @@ async fn cmd_secure_template(ctx: BridgeCtx, req: CmdSecureTemplateReq) -> Resul
|
||||
.map_err(err)
|
||||
}
|
||||
|
||||
async fn cmd_default_headers(_ctx: BridgeCtx, _req: CmdDefaultHeadersReq) -> Result<Vec<HttpRequestHeader>> {
|
||||
async fn cmd_default_headers(_ctx: BridgeCtx, _req: EmptyReq) -> Result<Vec<HttpRequestHeader>> {
|
||||
Ok(default_headers())
|
||||
}
|
||||
|
||||
// -- Plugins --
|
||||
|
||||
async fn cmd_get_themes(ctx: BridgeCtx, _req: CmdGetThemesReq) -> Result<Vec<GetThemesResponse>> {
|
||||
async fn cmd_get_themes(ctx: BridgeCtx, _req: EmptyReq) -> Result<Vec<GetThemesResponse>> {
|
||||
// Themes are optional: the TypeScript package ships defaults, and an empty
|
||||
// list still renders. Don't fail boot when the runtime is down.
|
||||
let Ok(plugins) = ctx.plugins() else {
|
||||
@@ -597,13 +785,19 @@ async fn cmd_get_themes(ctx: BridgeCtx, _req: CmdGetThemesReq) -> Result<Vec<Get
|
||||
plugins.get_themes(&ctx.plugin_context()).await.map_err(err)
|
||||
}
|
||||
|
||||
async fn cmd_plugin_init_errors(ctx: BridgeCtx, _req: CmdPluginInitErrorsReq) -> Result<Vec<(String, String)>> {
|
||||
async fn cmd_plugin_init_errors(ctx: BridgeCtx, _req: EmptyReq) -> Result<Vec<(String, String)>> {
|
||||
let Ok(plugins) = ctx.plugins() else {
|
||||
return Ok(Vec::new());
|
||||
};
|
||||
Ok(plugins.take_init_errors().await)
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CmdPluginInfoReq {
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
async fn cmd_plugin_info(ctx: BridgeCtx, req: CmdPluginInfoReq) -> Result<PluginMetadata> {
|
||||
let plugin = ctx.state.db().get_plugin(&req.id).map_err(err)?;
|
||||
let plugins = ctx.plugins()?;
|
||||
@@ -616,11 +810,24 @@ async fn cmd_plugin_info(ctx: BridgeCtx, req: CmdPluginInfoReq) -> Result<Plugin
|
||||
|
||||
async fn cmd_template_function_summaries(
|
||||
ctx: BridgeCtx,
|
||||
_req: CmdTemplateFunctionSummariesReq,
|
||||
_req: EmptyReq,
|
||||
) -> Result<Vec<GetTemplateFunctionSummaryResponse>> {
|
||||
ctx.plugins()?.get_template_function_summaries(&ctx.plugin_context()).await.map_err(err)
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CmdTemplateFunctionConfigReq {
|
||||
pub function_name: String,
|
||||
pub values: HashMap<String, JsonPrimitive>,
|
||||
pub model: AnyModel,
|
||||
/// Sent by the frontend, unused here — same as the desktop, which takes it
|
||||
/// as `_environment_id`. Template function values are not pre-rendered the
|
||||
/// way auth values are.
|
||||
#[allow(dead_code)]
|
||||
pub environment_id: Option<String>,
|
||||
}
|
||||
|
||||
async fn cmd_template_function_config(
|
||||
ctx: BridgeCtx,
|
||||
req: CmdTemplateFunctionConfigReq,
|
||||
@@ -638,13 +845,22 @@ async fn cmd_template_function_config(
|
||||
|
||||
async fn cmd_get_http_authentication_summaries(
|
||||
ctx: BridgeCtx,
|
||||
_req: CmdGetHttpAuthenticationSummariesReq,
|
||||
_req: EmptyReq,
|
||||
) -> Result<Vec<GetHttpAuthenticationSummaryResponse>> {
|
||||
let results =
|
||||
ctx.plugins()?.get_http_authentication_summaries(&ctx.plugin_context()).await.map_err(err)?;
|
||||
Ok(results.into_iter().map(|(_, a)| a).collect())
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CmdGetHttpAuthenticationConfigReq {
|
||||
pub auth_name: String,
|
||||
pub values: HashMap<String, JsonPrimitive>,
|
||||
pub model: AnyModel,
|
||||
pub environment_id: Option<String>,
|
||||
}
|
||||
|
||||
async fn cmd_get_http_authentication_config(
|
||||
ctx: BridgeCtx,
|
||||
req: CmdGetHttpAuthenticationConfigReq,
|
||||
@@ -662,6 +878,16 @@ async fn cmd_get_http_authentication_config(
|
||||
.map_err(err)
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CmdCallHttpAuthenticationActionReq {
|
||||
pub auth_name: String,
|
||||
pub action_index: i32,
|
||||
pub values: HashMap<String, JsonPrimitive>,
|
||||
pub model: AnyModel,
|
||||
pub environment_id: Option<String>,
|
||||
}
|
||||
|
||||
async fn cmd_call_http_authentication_action(
|
||||
ctx: BridgeCtx,
|
||||
req: CmdCallHttpAuthenticationActionReq,
|
||||
@@ -726,11 +952,17 @@ async fn render_auth_values(
|
||||
|
||||
async fn cmd_http_request_actions(
|
||||
ctx: BridgeCtx,
|
||||
_req: CmdHttpRequestActionsReq,
|
||||
_req: EmptyReq,
|
||||
) -> Result<Vec<GetHttpRequestActionsResponse>> {
|
||||
ctx.plugins()?.get_http_request_actions(&ctx.plugin_context()).await.map_err(err)
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CmdCallHttpRequestActionReq {
|
||||
pub req: CallHttpRequestActionRequest,
|
||||
}
|
||||
|
||||
async fn cmd_call_http_request_action(
|
||||
ctx: BridgeCtx,
|
||||
req: CmdCallHttpRequestActionReq,
|
||||
@@ -765,11 +997,17 @@ async fn cmd_call_http_request_action(
|
||||
|
||||
async fn cmd_workspace_actions(
|
||||
ctx: BridgeCtx,
|
||||
_req: CmdWorkspaceActionsReq,
|
||||
_req: EmptyReq,
|
||||
) -> Result<Vec<GetWorkspaceActionsResponse>> {
|
||||
ctx.plugins()?.get_workspace_actions(&ctx.plugin_context()).await.map_err(err)
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CmdCallWorkspaceActionReq {
|
||||
pub req: CallWorkspaceActionRequest,
|
||||
}
|
||||
|
||||
async fn cmd_call_workspace_action(ctx: BridgeCtx, req: CmdCallWorkspaceActionReq) -> Result<()> {
|
||||
use yaak_plugins::events::CallWorkspaceActionArgs;
|
||||
|
||||
@@ -783,10 +1021,16 @@ async fn cmd_call_workspace_action(ctx: BridgeCtx, req: CmdCallWorkspaceActionRe
|
||||
.map_err(err)
|
||||
}
|
||||
|
||||
async fn cmd_folder_actions(ctx: BridgeCtx, _req: CmdFolderActionsReq) -> Result<Vec<GetFolderActionsResponse>> {
|
||||
async fn cmd_folder_actions(ctx: BridgeCtx, _req: EmptyReq) -> Result<Vec<GetFolderActionsResponse>> {
|
||||
ctx.plugins()?.get_folder_actions(&ctx.plugin_context()).await.map_err(err)
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CmdCallFolderActionReq {
|
||||
pub req: CallFolderActionRequest,
|
||||
}
|
||||
|
||||
async fn cmd_call_folder_action(ctx: BridgeCtx, req: CmdCallFolderActionReq) -> Result<()> {
|
||||
use yaak_plugins::events::CallFolderActionArgs;
|
||||
|
||||
@@ -802,6 +1046,13 @@ async fn cmd_call_folder_action(ctx: BridgeCtx, req: CmdCallFolderActionReq) ->
|
||||
|
||||
// -- Import --
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CmdCurlToRequestReq {
|
||||
pub command: String,
|
||||
pub workspace_id: String,
|
||||
}
|
||||
|
||||
async fn cmd_curl_to_request(ctx: BridgeCtx, req: CmdCurlToRequestReq) -> Result<HttpRequest> {
|
||||
let import_result =
|
||||
ctx.plugins()?.import_data(&ctx.plugin_context(), &req.command).await.map_err(err)?;
|
||||
@@ -818,6 +1069,12 @@ async fn cmd_curl_to_request(ctx: BridgeCtx, req: CmdCurlToRequestReq) -> Result
|
||||
Ok(request)
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CmdImportDataReq {
|
||||
pub file_path: String,
|
||||
}
|
||||
|
||||
/// Import from a path on the *bridge's* machine.
|
||||
///
|
||||
/// The desktop gets this path from a native file dialog. A tab has no way to
|
||||
@@ -846,131 +1103,60 @@ async fn cmd_import_data(ctx: BridgeCtx, req: CmdImportDataReq) -> Result<BatchU
|
||||
.map_err(err)
|
||||
}
|
||||
|
||||
// -- Not on this host --
|
||||
rpc_commands! {
|
||||
cmd_metadata,
|
||||
cmd_default_headers,
|
||||
cmd_get_themes,
|
||||
cmd_plugin_init_errors,
|
||||
cmd_plugin_info,
|
||||
|
||||
/// Commands the bridge does not implement. Each still gets an adapter, so the
|
||||
/// schema stays fully covered and the frontend receives a structured error
|
||||
/// naming the command and this host rather than a bare "unknown command".
|
||||
///
|
||||
/// One list, two uses: `unsupported_commands!` emits both the adapters and the
|
||||
/// `UNSUPPORTED_COMMANDS` array `implemented_commands` subtracts.
|
||||
macro_rules! unsupported_commands {
|
||||
( $( $name:ident ( $req:ty ) ),* $(,)? ) => {
|
||||
// The stub never produces a value, so it doesn't need to name the
|
||||
// response type — which keeps git, gRPC and WebSocket crates out of a
|
||||
// binary that will never call them. `Never` serializes fine.
|
||||
$( async fn $name(_ctx: BridgeCtx, _req: $req) -> Result<Never> {
|
||||
Err(unsupported_command(stringify!($name)))
|
||||
} )*
|
||||
pub const UNSUPPORTED_COMMANDS: &[&str] = &[ $( stringify!($name), )* ];
|
||||
};
|
||||
models_upsert,
|
||||
models_delete,
|
||||
models_duplicate,
|
||||
models_get_settings,
|
||||
models_workspace_models,
|
||||
models_websocket_events,
|
||||
models_grpc_events,
|
||||
models_get_graphql_introspection,
|
||||
models_upsert_graphql_introspection,
|
||||
cmd_get_workspace_meta,
|
||||
|
||||
cmd_send_http_request,
|
||||
cmd_send_ephemeral_request,
|
||||
|
||||
cmd_http_response_body,
|
||||
cmd_http_response_body_path,
|
||||
cmd_http_request_body,
|
||||
cmd_get_http_response_events,
|
||||
cmd_get_sse_events,
|
||||
cmd_delete_all_http_responses,
|
||||
cmd_delete_send_history,
|
||||
|
||||
cmd_format_json,
|
||||
cmd_format_graphql,
|
||||
cmd_render_template,
|
||||
cmd_template_tokens_to_string,
|
||||
cmd_decrypt_template,
|
||||
cmd_secure_template,
|
||||
|
||||
cmd_template_function_summaries,
|
||||
cmd_template_function_config,
|
||||
cmd_get_http_authentication_summaries,
|
||||
cmd_get_http_authentication_config,
|
||||
cmd_call_http_authentication_action,
|
||||
|
||||
cmd_http_request_actions,
|
||||
cmd_call_http_request_action,
|
||||
cmd_workspace_actions,
|
||||
cmd_call_workspace_action,
|
||||
cmd_folder_actions,
|
||||
cmd_call_folder_action,
|
||||
|
||||
cmd_curl_to_request,
|
||||
cmd_import_data,
|
||||
}
|
||||
|
||||
/// A value that cannot exist. The unsupported adapters return `Result<Never>`
|
||||
/// and always take the `Err` branch, so `rpc_handler_async!` has something
|
||||
/// serializable to name without a real response type ever being constructed.
|
||||
#[derive(serde::Serialize)]
|
||||
enum Never {}
|
||||
|
||||
unsupported_commands! {
|
||||
// Multi-window. A tab is one window; Settings opens through this on the desktop and is therefore unreachable in the browser today.
|
||||
cmd_new_child_window(CmdNewChildWindowReq),
|
||||
cmd_new_main_window(CmdNewMainWindowReq),
|
||||
// gRPC and WebSocket sending.
|
||||
cmd_grpc_reflect(CmdGrpcReflectReq),
|
||||
cmd_grpc_go(CmdGrpcGoReq),
|
||||
cmd_grpc_request_actions(CmdGrpcRequestActionsReq),
|
||||
cmd_call_grpc_request_action(CmdCallGrpcRequestActionReq),
|
||||
cmd_delete_all_grpc_connections(CmdDeleteAllGrpcConnectionsReq),
|
||||
cmd_ws_connect(CmdWsConnectReq),
|
||||
cmd_ws_send(CmdWsSendReq),
|
||||
cmd_ws_close(CmdWsCloseReq),
|
||||
cmd_ws_delete_connections(CmdWsDeleteConnectionsReq),
|
||||
cmd_websocket_request_actions(CmdWebsocketRequestActionsReq),
|
||||
cmd_call_websocket_request_action(CmdCallWebsocketRequestActionReq),
|
||||
// Git-backed workspaces.
|
||||
cmd_git_checkout(CmdGitCheckoutReq),
|
||||
cmd_git_branch(CmdGitBranchReq),
|
||||
cmd_git_delete_branch(CmdGitDeleteBranchReq),
|
||||
cmd_git_delete_remote_branch(CmdGitDeleteRemoteBranchReq),
|
||||
cmd_git_merge_branch(CmdGitMergeBranchReq),
|
||||
cmd_git_rename_branch(CmdGitRenameBranchReq),
|
||||
cmd_git_status(CmdGitStatusReq),
|
||||
cmd_git_branch_info(CmdGitBranchInfoReq),
|
||||
cmd_git_worktree_status(CmdGitWorktreeStatusReq),
|
||||
cmd_git_log(CmdGitLogReq),
|
||||
cmd_git_log_for_file(CmdGitLogForFileReq),
|
||||
cmd_git_file_diff_for_commit(CmdGitFileDiffForCommitReq),
|
||||
cmd_git_initialize(CmdGitInitializeReq),
|
||||
cmd_git_clone(CmdGitCloneReq),
|
||||
cmd_git_commit(CmdGitCommitReq),
|
||||
cmd_git_fetch_all(CmdGitFetchAllReq),
|
||||
cmd_git_push(CmdGitPushReq),
|
||||
cmd_git_pull(CmdGitPullReq),
|
||||
cmd_git_pull_force_reset(CmdGitPullForceResetReq),
|
||||
cmd_git_pull_merge(CmdGitPullMergeReq),
|
||||
cmd_git_add(CmdGitAddReq),
|
||||
cmd_git_unstage(CmdGitUnstageReq),
|
||||
cmd_git_reset_changes(CmdGitResetChangesReq),
|
||||
cmd_git_restore_files(CmdGitRestoreFilesReq),
|
||||
cmd_git_restore_file_from_commit(CmdGitRestoreFileFromCommitReq),
|
||||
cmd_git_add_credential(CmdGitAddCredentialReq),
|
||||
cmd_git_remotes(CmdGitRemotesReq),
|
||||
cmd_git_add_remote(CmdGitAddRemoteReq),
|
||||
cmd_git_rm_remote(CmdGitRmRemoteReq),
|
||||
cmd_git_watch_worktree_status(CmdGitWatchWorktreeStatusReq),
|
||||
// Filesystem sync.
|
||||
cmd_sync_calculate(CmdSyncCalculateReq),
|
||||
cmd_sync_calculate_fs(CmdSyncCalculateFsReq),
|
||||
cmd_sync_apply(CmdSyncApplyReq),
|
||||
cmd_sync_watch(CmdSyncWatchReq),
|
||||
// Workspace encryption.
|
||||
cmd_enable_encryption(CmdEnableEncryptionReq),
|
||||
cmd_disable_encryption(CmdDisableEncryptionReq),
|
||||
cmd_reveal_workspace_key(CmdRevealWorkspaceKeyReq),
|
||||
cmd_set_workspace_key(CmdSetWorkspaceKeyReq),
|
||||
// Things that need a local filesystem the tab can point at.
|
||||
cmd_export_data(CmdExportDataReq),
|
||||
cmd_save_response(CmdSaveResponseReq),
|
||||
cmd_save_base64_to_binary(CmdSaveBase64ToBinaryReq),
|
||||
cmd_plugins_install_from_directory(CmdPluginsInstallFromDirectoryReq),
|
||||
cmd_import_url(CmdImportUrlReq),
|
||||
// Desktop application management.
|
||||
cmd_restart(CmdRestartReq),
|
||||
cmd_check_for_updates(CmdCheckForUpdatesReq),
|
||||
cmd_dismiss_notification(CmdDismissNotificationReq),
|
||||
cmd_send_feedback(CmdSendFeedbackReq),
|
||||
cmd_plugins_search(CmdPluginsSearchReq),
|
||||
cmd_plugins_install(CmdPluginsInstallReq),
|
||||
cmd_plugins_uninstall(CmdPluginsUninstallReq),
|
||||
cmd_plugins_updates(CmdPluginsUpdatesReq),
|
||||
cmd_plugins_update_all(CmdPluginsUpdateAllReq),
|
||||
cmd_reload_plugins(CmdReloadPluginsReq),
|
||||
}
|
||||
|
||||
// -- The router --
|
||||
|
||||
/// Every command in the schema, wired to an adapter here.
|
||||
///
|
||||
/// The list comes from `yaak_rpc_schema`, so this host cannot silently miss a
|
||||
/// command the frontend knows about: a schema entry with no adapter below is a
|
||||
/// compile error, not a runtime "unknown command". Commands the bridge does not
|
||||
/// support still get an adapter — one that says so — which is what lets the
|
||||
/// frontend tell a host that will never do git from one that is out of date.
|
||||
macro_rules! register_commands {
|
||||
( $( $name:ident ( $req:ty ) -> $res:ty ),* $(,)? ) => {
|
||||
pub fn build_router() -> RpcRouter<BridgeCtx> {
|
||||
let mut router = RpcRouter::new();
|
||||
$( router.register(stringify!($name), rpc_handler_async!($name)); )*
|
||||
router
|
||||
}
|
||||
};
|
||||
}
|
||||
yaak_rpc_schema::with_commands!(register_commands);
|
||||
|
||||
/// The names of the commands this host actually implements — everything in
|
||||
/// the schema minus the ones whose adapter is `unsupported`. Reported to the
|
||||
/// browser so it can fail fast with a clear message.
|
||||
/// Command names this host implements, for the capability report.
|
||||
pub fn implemented_commands(router: &RpcRouter<BridgeCtx>) -> Vec<String> {
|
||||
let unsupported: std::collections::HashSet<&str> =
|
||||
UNSUPPORTED_COMMANDS.iter().copied().collect();
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
//! The bridge's RPC surface.
|
||||
//!
|
||||
//! Same envelope, same command names, same request and response types as the
|
||||
//! desktop — all of that comes from `yaak_rpc_schema` — dispatched through the
|
||||
//! Same envelope and same command names as the desktop, dispatched through the
|
||||
//! same `RpcRouter`. Only the adapters differ: the desktop's take a Tauri
|
||||
//! window and read the workspace off its URL, while these take a `BridgeCtx`
|
||||
//! carrying the connected tab's reported URL. The bodies underneath call the
|
||||
//! same engine functions in `yaak`, `yaak-models` and `yaak-plugins`.
|
||||
//!
|
||||
//! The router is built from the schema's full command list, so every command
|
||||
//! the frontend knows has an adapter here — the ones this host doesn't
|
||||
//! implement return a structured error naming the command and the host, and
|
||||
//! the frontend surfaces "not supported by the Yaak Bridge" instead of a bare
|
||||
//! failure. Enough is implemented to boot, edit, send and inspect.
|
||||
//! This is a subset — enough to boot, edit, send and inspect. Anything not
|
||||
//! registered here still gets a well-formed answer: `unsupported_command`
|
||||
//! turns it into an RPC error naming the command and this host, so the frontend
|
||||
//! surfaces "not supported by the Yaak Bridge" instead of a bare failure.
|
||||
|
||||
mod commands;
|
||||
|
||||
@@ -56,6 +54,87 @@ pub fn build_router() -> RpcRouter<BridgeCtx> {
|
||||
commands::build_router()
|
||||
}
|
||||
|
||||
/// Every command the desktop has that the bridge does not implement.
|
||||
///
|
||||
/// Registered explicitly rather than left to fall through to "unknown command",
|
||||
/// so the message says *why* — the frontend can tell a host that will never
|
||||
/// support git from one that is simply out of date.
|
||||
pub const UNSUPPORTED_COMMANDS: &[&str] = &[
|
||||
// Multi-window. A tab is one window; Settings opens through this on the
|
||||
// desktop and is therefore unreachable in the browser today.
|
||||
"cmd_new_child_window",
|
||||
"cmd_new_main_window",
|
||||
// gRPC and WebSocket sending.
|
||||
"cmd_grpc_reflect",
|
||||
"cmd_grpc_go",
|
||||
"cmd_grpc_request_actions",
|
||||
"cmd_call_grpc_request_action",
|
||||
"cmd_delete_all_grpc_connections",
|
||||
"cmd_ws_connect",
|
||||
"cmd_ws_send",
|
||||
"cmd_ws_close",
|
||||
"cmd_ws_delete_connections",
|
||||
"cmd_websocket_request_actions",
|
||||
"cmd_call_websocket_request_action",
|
||||
// Git-backed workspaces.
|
||||
"cmd_git_checkout",
|
||||
"cmd_git_branch",
|
||||
"cmd_git_delete_branch",
|
||||
"cmd_git_delete_remote_branch",
|
||||
"cmd_git_merge_branch",
|
||||
"cmd_git_rename_branch",
|
||||
"cmd_git_status",
|
||||
"cmd_git_branch_info",
|
||||
"cmd_git_worktree_status",
|
||||
"cmd_git_log",
|
||||
"cmd_git_log_for_file",
|
||||
"cmd_git_file_diff_for_commit",
|
||||
"cmd_git_initialize",
|
||||
"cmd_git_clone",
|
||||
"cmd_git_commit",
|
||||
"cmd_git_fetch_all",
|
||||
"cmd_git_push",
|
||||
"cmd_git_pull",
|
||||
"cmd_git_pull_force_reset",
|
||||
"cmd_git_pull_merge",
|
||||
"cmd_git_add",
|
||||
"cmd_git_unstage",
|
||||
"cmd_git_reset_changes",
|
||||
"cmd_git_restore_files",
|
||||
"cmd_git_restore_file_from_commit",
|
||||
"cmd_git_add_credential",
|
||||
"cmd_git_remotes",
|
||||
"cmd_git_add_remote",
|
||||
"cmd_git_rm_remote",
|
||||
"cmd_git_watch_worktree_status",
|
||||
// Filesystem sync.
|
||||
"cmd_sync_calculate",
|
||||
"cmd_sync_calculate_fs",
|
||||
"cmd_sync_apply",
|
||||
"cmd_sync_watch",
|
||||
// Workspace encryption.
|
||||
"cmd_enable_encryption",
|
||||
"cmd_disable_encryption",
|
||||
"cmd_reveal_workspace_key",
|
||||
"cmd_set_workspace_key",
|
||||
// Things that need a local filesystem the tab can point at.
|
||||
"cmd_export_data",
|
||||
"cmd_save_response",
|
||||
"cmd_save_base64_to_binary",
|
||||
"cmd_plugins_install_from_directory",
|
||||
// Desktop application management.
|
||||
"cmd_restart",
|
||||
"cmd_check_for_updates",
|
||||
"cmd_dismiss_notification",
|
||||
"cmd_send_feedback",
|
||||
"cmd_plugins_search",
|
||||
"cmd_plugins_install",
|
||||
"cmd_plugins_uninstall",
|
||||
"cmd_plugins_updates",
|
||||
"cmd_plugins_update_all",
|
||||
"cmd_reload_plugins",
|
||||
];
|
||||
|
||||
pub fn unsupported_command(cmd: &str) -> RpcError {
|
||||
RpcError {
|
||||
message: format!("`{cmd}` is not supported on this host (Yaak Bridge)"),
|
||||
|
||||
@@ -73,7 +73,6 @@ url = "2"
|
||||
tokio-util = { version = "0.7", features = ["codec"] }
|
||||
ts-rs = { workspace = true }
|
||||
yaak-rpc = { workspace = true }
|
||||
yaak-rpc-schema = { workspace = true }
|
||||
uuid = "1.12.1"
|
||||
yaak-api = { workspace = true }
|
||||
yaak-common = { workspace = true }
|
||||
|
||||
crates/common/yaak-rpc-schema/bindings/gen_rpc.ts → crates-tauri/yaak-app-client/bindings/gen_rpc.ts
Generated
+5
-11
File diff suppressed because one or more lines are too long
+4
@@ -1,5 +1,7 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type GitWatchResult = { unlistenEvent: string, };
|
||||
|
||||
export type PluginUpdateInfo = { name: string, currentVersion: string, latestVersion: string, };
|
||||
|
||||
export type PluginUpdateNotification = { updateCount: number, plugins: Array<PluginUpdateInfo>, };
|
||||
@@ -10,6 +12,8 @@ export type UpdateResponse = { "type": "ack" } | { "type": "action", action: Upd
|
||||
|
||||
export type UpdateResponseAction = "install" | "skip";
|
||||
|
||||
export type WatchResult = { unlistenEvent: string, };
|
||||
|
||||
export type YaakNotification = { timestamp: string, timeout: number | null, id: string, title: string | null, message: string, color: string | null, action: YaakNotificationAction | null, };
|
||||
|
||||
export type YaakNotificationAction = { label: string, url: string, };
|
||||
|
||||
Generated
Generated
@@ -1,4 +1,4 @@
|
||||
// ts-rs owns bindings/index.ts and rewrites it on export. What remains here
|
||||
// after the RPC schema moved to @yaakapp-internal/rpc-schema is the
|
||||
// desktop-only surface: updater and notification types.
|
||||
// ts-rs owns bindings/index.ts and rewrites it on export, so this hand-written
|
||||
// entry point is where the generated files come together.
|
||||
export * from "./bindings/gen_rpc";
|
||||
export * from "./bindings/index";
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::error::{Error, Result};
|
||||
use chrono::Utc;
|
||||
use log::{debug, error, warn};
|
||||
use notify::Watcher;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::Path;
|
||||
use std::sync::mpsc;
|
||||
use std::time::Duration;
|
||||
@@ -9,11 +10,18 @@ use tauri::{AppHandle, Listener, Runtime};
|
||||
use tokio::select;
|
||||
use tokio::sync::watch;
|
||||
use tokio::time::sleep;
|
||||
use ts_rs::TS;
|
||||
use yaak_git::{GitWorktreeStatus, git_path_is_ignored, git_repository_paths, git_worktree_status};
|
||||
use yaak_rpc_schema::GitWatchResult;
|
||||
|
||||
const GIT_STATUS_COALESCE_WINDOW: Duration = Duration::from_millis(250);
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "index.ts")]
|
||||
pub(crate) struct GitWatchResult {
|
||||
unlisten_event: String,
|
||||
}
|
||||
|
||||
pub(crate) async fn watch_git_worktree_status<R, F>(
|
||||
app_handle: AppHandle<R>,
|
||||
dir: &Path,
|
||||
|
||||
@@ -8,6 +8,7 @@ use crate::import::{import_data, import_url};
|
||||
use crate::models_ext::{BlobManagerExt, QueryManagerExt};
|
||||
use crate::notifications::YaakNotifier;
|
||||
use crate::render::{render_grpc_request, render_json_value, render_template};
|
||||
use crate::rpc_ext::EphemeralHttpResponse;
|
||||
use crate::updates::{UpdateMode, UpdateTrigger, YaakUpdater};
|
||||
use crate::uri_scheme::handle_deep_link;
|
||||
use error::Result as YaakResult;
|
||||
@@ -56,7 +57,6 @@ use yaak_plugins::events::{
|
||||
use yaak_plugins::manager::PluginManager;
|
||||
use yaak_plugins::plugin_meta::{PluginMetadata, get_plugin_meta};
|
||||
use yaak_plugins::template_callback::PluginTemplateCallback;
|
||||
use yaak_rpc_schema::{AppMetaData, EphemeralHttpResponse};
|
||||
use yaak_sse::sse::ServerSentEvent;
|
||||
use yaak_tauri_utils::window::WorkspaceWindowTrait;
|
||||
use yaak_templates::format_json::format_json;
|
||||
@@ -184,6 +184,22 @@ impl<R: Runtime> PluginContextExt<R> for WebviewWindow<R> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, ts_rs::TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "gen_rpc.ts")]
|
||||
pub struct AppMetaData {
|
||||
is_dev: bool,
|
||||
version: String,
|
||||
cli_version: Option<String>,
|
||||
name: String,
|
||||
app_data_dir: String,
|
||||
app_log_dir: String,
|
||||
vendored_plugin_dir: String,
|
||||
default_project_dir: String,
|
||||
feature_updater: bool,
|
||||
feature_license: bool,
|
||||
}
|
||||
|
||||
async fn cmd_metadata<R: Runtime>(app_handle: AppHandle<R>) -> YaakResult<AppMetaData> {
|
||||
let app_data_dir = app_handle.path().app_data_dir()?;
|
||||
let app_log_dir = app_handle.path().app_log_dir()?;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,10 +6,11 @@ use crate::error::Result;
|
||||
use crate::models_ext::{BlobManagerExt, QueryManagerExt};
|
||||
use chrono::Utc;
|
||||
use log::warn;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::Path;
|
||||
use tauri::{AppHandle, Listener, Runtime};
|
||||
use tokio::sync::watch;
|
||||
use yaak_rpc_schema::WatchResult;
|
||||
use ts_rs::TS;
|
||||
use yaak_sync::error::Error::InvalidSyncDirectory;
|
||||
use yaak_sync::sync::{
|
||||
FsCandidate, SyncOp, apply_sync_ops, apply_sync_state_ops, compute_sync_ops, get_db_candidates,
|
||||
@@ -56,6 +57,13 @@ pub(crate) async fn cmd_sync_apply<R: Runtime>(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "index.ts")]
|
||||
pub(crate) struct WatchResult {
|
||||
unlisten_event: String,
|
||||
}
|
||||
|
||||
pub(crate) async fn sync_watch<R, F>(
|
||||
app_handle: AppHandle<R>,
|
||||
sync_dir: &Path,
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
[package]
|
||||
name = "yaak-rpc-schema"
|
||||
version = "0.0.0"
|
||||
edition = "2024"
|
||||
authors = ["Gregory Schier"]
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
ts-rs = { workspace = true }
|
||||
yaak-git = { workspace = true }
|
||||
yaak-grpc = { workspace = true }
|
||||
yaak-models = { workspace = true }
|
||||
yaak-plugins = { workspace = true }
|
||||
yaak-sse = { workspace = true }
|
||||
yaak-sync = { workspace = true }
|
||||
yaak-templates = { workspace = true }
|
||||
yaak-ws = { workspace = true }
|
||||
@@ -1,44 +0,0 @@
|
||||
# yaak-rpc-schema
|
||||
|
||||
The wire schema for the app's RPC surface: every command name, its request
|
||||
payload, and its response type, declared once.
|
||||
|
||||
Every host that serves the Yaak UI — the desktop app today, the browser bridge
|
||||
and anything after it — imports these types and implements the commands against
|
||||
them. That is what keeps a request's shape from drifting between hosts, and it
|
||||
is why the TypeScript bindings (`bindings/gen_rpc.ts`, exposed to the frontend
|
||||
as `@yaakapp-internal/rpc-schema`) are generated from one place.
|
||||
|
||||
Nothing here depends on Tauri or on any host. Request structs are plain data,
|
||||
and so are the few response types declared here rather than in an engine crate.
|
||||
Command *bodies* live with the host that runs them.
|
||||
|
||||
## Adding a command
|
||||
|
||||
1. Add its request struct and an entry in `with_commands!` in `src/lib.rs`.
|
||||
2. Write the adapter in each host — the desktop's live in
|
||||
`crates-tauri/yaak-app-client/src/rpc_ext.rs`. A host that does not support
|
||||
the command still has to say so; a missing adapter fails to compile.
|
||||
3. Regenerate the bindings: `cargo test -p yaak-rpc-schema` writes
|
||||
`bindings/gen_rpc.ts`, which is committed.
|
||||
|
||||
## How hosts consume the list
|
||||
|
||||
`with_commands!` takes the name of a `macro_rules!` macro and calls it with the
|
||||
full `name(Req) -> Res` list. Each host writes a small macro that receives that
|
||||
list and builds its router:
|
||||
|
||||
```rust
|
||||
macro_rules! register_commands {
|
||||
( $( $name:ident ( $req:ty ) -> $res:ty ),* $(,)? ) => {
|
||||
pub fn build_router() -> RpcRouter<MyCtx> {
|
||||
let mut router = RpcRouter::new();
|
||||
$( router.register(stringify!($name), rpc_handler_async!($name)); )*
|
||||
router
|
||||
}
|
||||
};
|
||||
}
|
||||
yaak_rpc_schema::with_commands!(register_commands);
|
||||
```
|
||||
|
||||
The schema decides *what* commands exist; the host decides *how* each one runs.
|
||||
@@ -1,4 +0,0 @@
|
||||
// The RPC wire schema, generated by ts-rs from the Rust declarations in
|
||||
// src/lib.rs. `RpcSchema` maps every command name to its (request, response)
|
||||
// pair; the app's `rpc()` helper derives its command union from it.
|
||||
export * from "./bindings/gen_rpc";
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"name": "@yaakapp-internal/rpc-schema",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"main": "index.ts"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
import { platform } from "@yaakapp-internal/platform";
|
||||
import type { WatchResult } from "@yaakapp-internal/rpc-schema";
|
||||
import type { WatchResult } from "@yaakapp-internal/tauri-client";
|
||||
import { SyncOp } from "./bindings/gen_sync";
|
||||
import { WatchEvent } from "./bindings/gen_watch";
|
||||
|
||||
|
||||
@@ -58,7 +58,6 @@
|
||||
"crates-tauri/yaak-fonts",
|
||||
"crates-tauri/yaak-license",
|
||||
"crates-tauri/yaak-mac-window",
|
||||
"crates/common/yaak-rpc-schema",
|
||||
"crates/yaak-crypto",
|
||||
"crates/yaak-git",
|
||||
"crates/yaak-models",
|
||||
|
||||
@@ -213,11 +213,6 @@ export function createBridgePlatform(baseUrl: string, token: string | null): Pla
|
||||
readDir: async () => {
|
||||
throw unsupported("Browsing the filesystem");
|
||||
},
|
||||
// Only ever called with a path this host handed out, and this host has
|
||||
// no dialog or drag-drop to hand one out with.
|
||||
readText: async () => {
|
||||
throw unsupported("Reading a local file");
|
||||
},
|
||||
// No filesystem here, so a path is just a string this host echoes back.
|
||||
url: (path) => path,
|
||||
basename: async (path) => path.split(/[/\\]/).pop() ?? path,
|
||||
|
||||
@@ -20,16 +20,18 @@ declare global {
|
||||
interface Window {
|
||||
__TAURI_INTERNALS__?: unknown;
|
||||
}
|
||||
|
||||
// Declared here rather than by depending on Vite's types: this package is
|
||||
// consumed by a bundler that provides them, and only this one variable.
|
||||
interface ImportMeta {
|
||||
readonly env?: Record<string, string | undefined>;
|
||||
}
|
||||
}
|
||||
|
||||
function bridgeUrl(): string {
|
||||
// Set when the frontend runs on a Vite dev server and the bridge is on its
|
||||
// own port. When the bridge serves the built app, they share an origin.
|
||||
// Vite inlines `import.meta.env` at build time. Read it through a cast so
|
||||
// this package typechecks on its own without depending on Vite's types, and
|
||||
// still picks up the real declaration when the app compiles it.
|
||||
const env = (import.meta as { env?: Record<string, string | undefined> }).env;
|
||||
const configured = env?.VITE_YAAK_BRIDGE_URL;
|
||||
const configured = import.meta.env?.VITE_YAAK_BRIDGE_URL;
|
||||
return (configured ?? window.location.origin).replace(/\/$/, "");
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user