feat(frontend): adapter web HTTP+WebSocket derrière les ports (#13)
Lot F1 du chantier server/client mode : nouvel adaptateur web branché derrière les ports d'invocation et de flux live, permettant au frontend de dialoguer avec le backend via HTTP + WebSocket en mode client/serveur. Le mode desktop (Tauri IPC) reste inchangé. - frontend/src/adapters/http : invoker HTTP, client live WebSocket, gateways request/response et stream, frames, garde unsupported (7 fichiers + 2 tests). - frontend/src/app : câblage DI (di.tsx) et son test, typage vite-env.d.ts. Validé : build vert, garde no-direct-invoke verte, 724 tests verts, desktop inchangé. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
110
frontend/src/adapters/http/index.ts
Normal file
110
frontend/src/adapters/http/index.ts
Normal file
@ -0,0 +1,110 @@
|
||||
/**
|
||||
* Web (client/server) adapter set — ticket #13, lot F1.
|
||||
*
|
||||
* `createHttpWsGateways()` is the third gateway implementation, alongside
|
||||
* `createTauriGateways()` (desktop) and `createMockGateways()` (tests/dev). It
|
||||
* wires every UI port to the shared backend core over HTTP request/response and a
|
||||
* single multiplexed WebSocket, exactly behind the *unchanged* ports — no
|
||||
* component is aware of it (selected in `app/di.tsx`).
|
||||
*
|
||||
* Scope reminder (F1): request/response gateways are fully implemented; the WS
|
||||
* PTY/chat streams are a contract-conformant **skeleton** (full round-trip lands
|
||||
* in F3/B5/B6 once a server exists — B3/B4). Desktop-only surfaces (pickFolder,
|
||||
* OS windows, focused-project, remote) fail with an explicit "unsupported on web"
|
||||
* error or an inert stub, never a Tauri call.
|
||||
*/
|
||||
|
||||
import type { Gateways } from "@/ports";
|
||||
import { LocalStorageUiPreferencesGateway } from "../uiPreferences";
|
||||
import { HttpInvoker } from "./httpInvoker";
|
||||
import { WsLiveClient } from "./wsLiveClient";
|
||||
import {
|
||||
HttpConversationGateway,
|
||||
HttpEmbedderGateway,
|
||||
HttpGitGateway,
|
||||
HttpInputGateway,
|
||||
HttpLayoutGateway,
|
||||
HttpMemoryGateway,
|
||||
HttpModelServerGateway,
|
||||
HttpPermissionGateway,
|
||||
HttpProfileGateway,
|
||||
HttpProjectGateway,
|
||||
HttpSkillGateway,
|
||||
HttpTemplateGateway,
|
||||
HttpWorkStateGateway,
|
||||
} from "./requestResponseGateways";
|
||||
import {
|
||||
HttpAgentGateway,
|
||||
HttpSystemGateway,
|
||||
HttpTerminalGateway,
|
||||
HttpTicketGateway,
|
||||
} from "./streamGateways";
|
||||
import {
|
||||
WebFocusedProjectGateway,
|
||||
WebRemoteGateway,
|
||||
WebWindowGateway,
|
||||
} from "./unsupported";
|
||||
|
||||
/** Endpoint configuration for the web transport. */
|
||||
export interface HttpWsGatewaysConfig {
|
||||
/** Backend HTTP base URL (no trailing slash). Defaults to the page origin. */
|
||||
baseUrl?: string;
|
||||
/** WebSocket base URL. Defaults to the page origin with `http(s)`→`ws(s)`. */
|
||||
wsUrl?: string;
|
||||
/** Bearer token from pairing (ticket #13 auth). */
|
||||
token?: string;
|
||||
}
|
||||
|
||||
/** Derives default `{baseUrl, wsUrl}` from `window.location` when available. */
|
||||
function resolveEndpoints(config: HttpWsGatewaysConfig): {
|
||||
baseUrl: string;
|
||||
wsUrl: string;
|
||||
} {
|
||||
const origin =
|
||||
typeof window !== "undefined" && window.location
|
||||
? window.location.origin
|
||||
: "http://localhost";
|
||||
const baseUrl = config.baseUrl ?? origin;
|
||||
const wsUrl =
|
||||
config.wsUrl ?? baseUrl.replace(/^http(s?):\/\//, (_m, s) => `ws${s}://`);
|
||||
return { baseUrl, wsUrl };
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the full set of web (HTTP+WS) gateways. `config` is optional; endpoints
|
||||
* default to the current page origin so a browser build talks to its own server.
|
||||
* The {@link HttpInvoker} and {@link WsLiveClient} are shared across gateways.
|
||||
*/
|
||||
export function createHttpWsGateways(config: HttpWsGatewaysConfig = {}): Gateways {
|
||||
const { baseUrl, wsUrl } = resolveEndpoints(config);
|
||||
const http = new HttpInvoker({ baseUrl, token: config.token });
|
||||
const ws = new WsLiveClient({ wsUrl, token: config.token });
|
||||
|
||||
return {
|
||||
system: new HttpSystemGateway(http, ws),
|
||||
agent: new HttpAgentGateway(http, ws),
|
||||
input: new HttpInputGateway(http),
|
||||
terminal: new HttpTerminalGateway(ws),
|
||||
project: new HttpProjectGateway(http),
|
||||
layout: new HttpLayoutGateway(http),
|
||||
git: new HttpGitGateway(http),
|
||||
remote: new WebRemoteGateway(),
|
||||
profile: new HttpProfileGateway(http),
|
||||
modelServer: new HttpModelServerGateway(http),
|
||||
template: new HttpTemplateGateway(http),
|
||||
skill: new HttpSkillGateway(http),
|
||||
memory: new HttpMemoryGateway(http),
|
||||
embedder: new HttpEmbedderGateway(http),
|
||||
permission: new HttpPermissionGateway(http),
|
||||
workState: new HttpWorkStateGateway(http),
|
||||
conversation: new HttpConversationGateway(http),
|
||||
ticket: new HttpTicketGateway(http, ws),
|
||||
window: new WebWindowGateway(),
|
||||
focusedProject: new WebFocusedProjectGateway(),
|
||||
// Frontend-owned UI prefs are transport-neutral (localStorage) — reuse as-is.
|
||||
uiPreferences: new LocalStorageUiPreferencesGateway(),
|
||||
};
|
||||
}
|
||||
|
||||
export { HttpInvoker } from "./httpInvoker";
|
||||
export { WsLiveClient } from "./wsLiveClient";
|
||||
Reference in New Issue
Block a user