Type the web sender with the generated model types

This commit is contained in:
Gregory Schier
2026-08-17 08:21:59 -07:00
parent 1216881bb3
commit e2ab4ee1b0
4 changed files with 39 additions and 66 deletions
+1
View File
@@ -15799,6 +15799,7 @@
"@tauri-apps/plugin-fs": "^2.5.1", "@tauri-apps/plugin-fs": "^2.5.1",
"@tauri-apps/plugin-opener": "^2.5.4", "@tauri-apps/plugin-opener": "^2.5.4",
"@tauri-apps/plugin-os": "^2.3.2", "@tauri-apps/plugin-os": "^2.3.2",
"@yaakapp-internal/models": "^1.0.0",
"@yaakapp-internal/rpc-schema": "^1.0.0", "@yaakapp-internal/rpc-schema": "^1.0.0",
"@yaakapp-internal/web": "^1.0.0" "@yaakapp-internal/web": "^1.0.0"
} }
+1
View File
@@ -9,6 +9,7 @@
"lint": "tsc --noEmit" "lint": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"@yaakapp-internal/models": "^1.0.0",
"@yaakapp-internal/rpc-schema": "^1.0.0", "@yaakapp-internal/rpc-schema": "^1.0.0",
"@yaakapp-internal/web": "^1.0.0", "@yaakapp-internal/web": "^1.0.0",
"@tauri-apps/api": "^2.11.0", "@tauri-apps/api": "^2.11.0",
+14 -12
View File
@@ -7,6 +7,13 @@
* is a change to the other, and the frame `type` tags are the versioning. * is a change to the other, and the frame `type` tags are the versioning.
*/ */
import type {
Cookie,
HttpRequest,
HttpResponseEventData,
HttpResponseHeader,
} from "@yaakapp-internal/models";
/* ------------------------------- location -------------------------------- */ /* ------------------------------- location -------------------------------- */
/** /**
@@ -30,7 +37,7 @@ export function proxySendUrl(): string {
/** The body of `POST /v1/http/send`. */ /** The body of `POST /v1/http/send`. */
export interface ProxyRequestBody { export interface ProxyRequestBody {
/** The rendered request, in the model shape (see `wire.rs` `SendRequest.request`). */ /** The rendered request, in the model shape (see `wire.rs` `SendRequest.request`). */
request: Record<string, unknown>; request: HttpRequest;
settings: { settings: {
validateCertificates: boolean; validateCertificates: boolean;
followRedirects: boolean; followRedirects: boolean;
@@ -39,16 +46,11 @@ export interface ProxyRequestBody {
storeCookies: boolean; storeCookies: boolean;
}; };
/** The jar's cookies to start from, or `null` for no jar at all. */ /** The jar's cookies to start from, or `null` for no jar at all. */
cookies: unknown[] | null; cookies: Cookie[] | null;
} }
/* -------------------------------- down ----------------------------------- */ /* -------------------------------- down ----------------------------------- */
interface WireHeader {
name: string;
value: string;
}
export interface ProxySendResponse { export interface ProxySendResponse {
type: "response"; type: "response";
status: number; status: number;
@@ -56,8 +58,8 @@ export interface ProxySendResponse {
url: string; url: string;
remoteAddr: string | null; remoteAddr: string | null;
version: string | null; version: string | null;
headers: WireHeader[]; headers: HttpResponseHeader[];
requestHeaders: WireHeader[]; requestHeaders: HttpResponseHeader[];
contentLength: number | null; contentLength: number | null;
elapsedHeaders: number; elapsedHeaders: number;
elapsedDns: number; elapsedDns: number;
@@ -65,7 +67,7 @@ export interface ProxySendResponse {
export type ProxyFrame = export type ProxyFrame =
/** A timeline event in the `http_response_event.event` shape. */ /** A timeline event in the `http_response_event.event` shape. */
| { type: "event"; event: unknown } | { type: "event"; event: HttpResponseEventData }
| ProxySendResponse | ProxySendResponse
/** A body chunk, decompressed, base64. */ /** A body chunk, decompressed, base64. */
| { type: "body"; data: string } | { type: "body"; data: string }
@@ -74,9 +76,9 @@ export type ProxyFrame =
elapsed: number; elapsed: number;
contentLength: number; contentLength: number;
contentLengthCompressed: number; contentLengthCompressed: number;
cookies: unknown[] | null; cookies: Cookie[] | null;
} }
| { type: "error"; message: string; cookies: unknown[] | null }; | { type: "error"; message: string; cookies: Cookie[] | null };
/** /**
* Yield frames from an NDJSON stream as they arrive. A partial trailing line is * Yield frames from an NDJSON stream as they arrive. A partial trailing line is
+23 -54
View File
@@ -21,65 +21,38 @@
* byte it returns is stored by this tab. * byte it returns is stored by this tab.
*/ */
// Types only: the models package imports this one at runtime, and a type import
// is erased, so there is no cycle.
import type {
Cookie,
CookieJar,
HttpRequest,
HttpResponse,
HttpResponseEventData,
} from "@yaakapp-internal/models";
import type { WorkerConnection } from "./connection"; import type { WorkerConnection } from "./connection";
import type { ProxyFrame, ProxyRequestBody, ProxySendResponse } from "./proxy"; import type { ProxyFrame, ProxyRequestBody, ProxySendResponse } from "./proxy";
import { proxySendUrl, readFrames } from "./proxy"; import { proxySendUrl, readFrames } from "./proxy";
/* -------------------------------- shapes --------------------------------- */ /* -------------------------------- shapes --------------------------------- */
// The model types this file writes, spelled out rather than imported from
// `@yaakapp-internal/models`: the platform package sits underneath the model
// package in the dependency graph and must not import it.
interface HttpResponseHeader {
name: string;
value: string;
}
/** /**
* The fields of the `http_response` model this sender writes as a send * The response row as this file knows it: what identifies it, plus whatever
* progresses. Every one is optional: a field this file never sets takes the * has been written so far. Every other field is optional and takes the model
* model layer's default, the same way the desktop's row does. Defaults live in * layer's default when absent, the same way the desktop's row does — defaults
* Rust, once. * live in Rust, once.
*/ */
interface ResponsePatch { type ResponseRow = Pick<HttpResponse, "model" | "requestId" | "workspaceId"> &
state?: "initialized" | "connected" | "closed"; Partial<Omit<HttpResponse, "model" | "requestId" | "workspaceId">>;
url?: string;
status?: number;
statusReason?: string | null;
version?: string | null;
remoteAddr?: string | null;
headers?: HttpResponseHeader[];
requestHeaders?: HttpResponseHeader[];
contentLength?: number | null;
contentLengthCompressed?: number | null;
elapsed?: number;
elapsedHeaders?: number;
elapsedDns?: number;
error?: string | null;
}
/** The row itself: what identifies it, plus whatever has been written so far. */ type ResponsePatch = Partial<HttpResponse>;
type ResponseRow = {
model: "http_response";
id?: string;
requestId: string;
workspaceId: string;
} & ResponsePatch;
interface CookieJarModel {
model: "cookie_jar";
id: string;
cookies: unknown[];
[key: string]: unknown;
}
/** What `prepare_http_send` (crates/yaak-web) hands back. */ /** What `prepare_http_send` (crates/yaak-web) hands back. */
interface PreparedHttpSend { interface PreparedHttpSend {
request: { url: string; [key: string]: unknown }; request: HttpRequest;
settings: ProxyRequestBody["settings"]; settings: ProxyRequestBody["settings"];
settingEvents: unknown[]; settingEvents: HttpResponseEventData[];
cookieJar: CookieJarModel | null; cookieJar: CookieJar | null;
} }
/** The desktop writes progress at most this often while a body streams in. */ /** The desktop writes progress at most this often while a body streams in. */
@@ -92,7 +65,7 @@ export async function sendHttpRequest(
requestId: string, requestId: string,
environmentId: string | null, environmentId: string | null,
cookieJarId: string | null, cookieJarId: string | null,
): Promise<unknown> { ): Promise<ResponseRow> {
// The response row exists before anything can go wrong, as on the desktop, so // The response row exists before anything can go wrong, as on the desktop, so
// a failure to render or to reach the proxy lands in the response pane as // a failure to render or to reach the proxy lands in the response pane as
// that response's error rather than as a toast that names no request. // that response's error rather than as a toast that names no request.
@@ -300,7 +273,7 @@ class ResponseWriter {
* which the whole timeline is known to be in the database. * which the whole timeline is known to be in the database.
*/ */
class TimelineWriter { class TimelineWriter {
private queue: unknown[] = []; private queue: HttpResponseEventData[] = [];
private inFlight: Promise<void> = Promise.resolve(); private inFlight: Promise<void> = Promise.resolve();
constructor( constructor(
@@ -309,7 +282,7 @@ class TimelineWriter {
private readonly workspaceId: string, private readonly workspaceId: string,
) {} ) {}
push(events: unknown[]): void { push(events: HttpResponseEventData[]): void {
if (events.length === 0) return; if (events.length === 0) return;
this.queue.push(...events); this.queue.push(...events);
this.inFlight = this.inFlight.then(() => this.drain()); this.inFlight = this.inFlight.then(() => this.drain());
@@ -331,11 +304,7 @@ class TimelineWriter {
} }
} }
async function persistCookies( async function persistCookies(db: WorkerConnection, jar: CookieJar, cookies: Cookie[]): Promise<void> {
db: WorkerConnection,
jar: CookieJarModel,
cookies: unknown[],
): Promise<void> {
// The desktop compares before writing so a jar edited mid-send isn't clobbered // The desktop compares before writing so a jar edited mid-send isn't clobbered
// by an unchanged copy. Structural equality is enough here: cookies are plain // by an unchanged copy. Structural equality is enough here: cookies are plain
// data and the proxy hands back the whole jar. // data and the proxy hands back the whole jar.