From 2b66107866763791d7fcc0b79381c3d3484e72d3 Mon Sep 17 00:00:00 2001 From: Chris Wanstrath Date: Fri, 3 Oct 2025 07:00:25 -0700 Subject: [PATCH] narrow Message types --- src/dispatch.ts | 10 ++++++---- src/js/browser.ts | 1 - src/js/commands.ts | 2 +- src/js/editor.ts | 5 +++-- src/js/scrollback.ts | 5 +++-- src/js/shell.ts | 3 ++- src/js/stream.ts | 18 +++++++++--------- src/js/websocket.ts | 7 ++++--- src/shared/types.ts | 40 +++++++++++++++++++++++++++++----------- src/stream.ts | 4 ++-- 10 files changed, 59 insertions(+), 36 deletions(-) diff --git a/src/dispatch.ts b/src/dispatch.ts index 908d2cd..2eea407 100644 --- a/src/dispatch.ts +++ b/src/dispatch.ts @@ -11,13 +11,15 @@ export async function dispatchMessage(ws: any, msg: Message) { console.log("<- receive", msg) switch (msg.type) { case "input": - await inputMessage(ws, msg as InputMessage); break + await inputMessage(ws, msg); break case "save-file": - await saveFileMessage(ws, msg as SaveFileMessage); break + await saveFileMessage(ws, msg); break - case "ui:mode": - setState("ui:mode", msg.data); break + case "session:update": + for (const key of Object.keys(msg.data)) + setState(key, msg.data[key]); + break default: send(ws, { type: "error", data: `unknown message: ${msg.type}` }) diff --git a/src/js/browser.ts b/src/js/browser.ts index 33ceeba..4991a02 100644 --- a/src/js/browser.ts +++ b/src/js/browser.ts @@ -58,7 +58,6 @@ function closeBrowser() { iframe.style.pointerEvents = "none" iframe.tabIndex = -1 iframe.classList.remove("fullscreen", "active") - iframe.style.border = "2px solid var(--c64-light-blue)" scrollback.append(iframe) controls.style.display = "none" diff --git a/src/js/commands.ts b/src/js/commands.ts index 1c55d37..80c8990 100644 --- a/src/js/commands.ts +++ b/src/js/commands.ts @@ -22,7 +22,7 @@ export const browserCommands: Record void | Promi mode: (mode?: string) => { if (!mode) { mode = document.body.dataset.mode === "tall" ? "cinema" : "tall" - send({ type: "ui:mode", data: mode }) + send({ type: "session:update", data: { "ui:mode": mode } }) } content.style.display = "" diff --git a/src/js/editor.ts b/src/js/editor.ts index 7c71f9f..5f06834 100644 --- a/src/js/editor.ts +++ b/src/js/editor.ts @@ -1,3 +1,4 @@ +import type { SaveFileMessage } from "../shared/types" import { scrollback } from "./dom" import { send } from "./websocket" import { focusInput } from "./focus" @@ -49,10 +50,10 @@ function keydownHandler(e: KeyboardEvent) { } else if ((e.ctrlKey && e.key === "s") || (e.ctrlKey && e.key === "Enter")) { e.preventDefault() send({ - id: editor.dataset.path, + id: editor.dataset.path || "/tmp.txt", type: "save-file", data: editor.value - }) + } as SaveFileMessage) } else if (e.key === "{") { if (editor.selectionStart !== editor.selectionEnd) { insertAroundSelection(editor, '{', '}') diff --git a/src/js/scrollback.ts b/src/js/scrollback.ts index d488375..3304e62 100644 --- a/src/js/scrollback.ts +++ b/src/js/scrollback.ts @@ -133,6 +133,7 @@ function handleInputClick(e: MouseEvent) { export function handleOutput(msg: Message) { const result = msg.data as CommandResult - setStatus(msg.id!, result.status) - addOutput(msg.id!, result.output) + const id = "id" in msg ? msg.id || "" : "" + setStatus(id, result.status) + addOutput(id, result.output) } diff --git a/src/js/shell.ts b/src/js/shell.ts index 46f3d32..63aa410 100644 --- a/src/js/shell.ts +++ b/src/js/shell.ts @@ -1,6 +1,7 @@ //// // The shell runs on the server and processes input, returning output. +import type { InputMessage } from "../shared/types" import { addInput, setStatus, addOutput } from "./scrollback" import { send } from "./websocket" import { randomId } from "../shared/utils" @@ -27,7 +28,7 @@ export async function runCommand(input: string) { if (result) addOutput(id, result) setStatus(id, "ok") } else { - send({ id, type: "input", data: input }) + send({ id, type: "input", data: input } as InputMessage) } } diff --git a/src/js/stream.ts b/src/js/stream.ts index 7d640e5..de43986 100644 --- a/src/js/stream.ts +++ b/src/js/stream.ts @@ -1,25 +1,25 @@ -import type { Message, CommandOutput } from "@/shared/types" +import type { StreamMessage } from "@/shared/types" import { addOutput, appendOutput, replaceOutput } from "./scrollback" -export function handleStreamStart(msg: Message) { - const id = msg.id! +export function handleStreamStart(msg: StreamMessage) { + const id = msg.id const status = document.querySelector(`[data-id="${id}"].input .status`) if (!status) return - addOutput(id, msg.data as CommandOutput) + addOutput(id, msg.data) status.classList.remove("yellow") status.classList.add("purple") } -export function handleStreamAppend(msg: Message) { - appendOutput(msg.id!, msg.data as CommandOutput) +export function handleStreamAppend(msg: StreamMessage) { + appendOutput(msg.id, msg.data) } -export function handleStreamReplace(msg: Message) { - replaceOutput(msg.id!, msg.data as CommandOutput) +export function handleStreamReplace(msg: StreamMessage) { + replaceOutput(msg.id, msg.data) } -export function handleStreamEnd(_msg: Message) { +export function handleStreamEnd(_msg: StreamMessage) { } diff --git a/src/js/websocket.ts b/src/js/websocket.ts index 9930f5d..d406752 100644 --- a/src/js/websocket.ts +++ b/src/js/websocket.ts @@ -9,7 +9,7 @@ import { addErrorMessage } from "./scrollback" const MAX_RETRIES = 5 let retries = 0 let connected = false -let msgQueue: Message[] = [] +let msgQueue: Omit[] = [] let ws: WebSocket | null = null @@ -30,14 +30,15 @@ export function startConnection() { } // send any message -export function send(msg: Message) { +export function send(msg: Omit) { if (!connected) { msgQueue.push(msg) startConnection() return } - if (!msg.session) msg.session = sessionId + if (!(msg as any).session) (msg as any).session = sessionId + ws?.readyState === 1 && ws.send(JSON.stringify(msg)) console.log("-> send", msg) } diff --git a/src/shared/types.ts b/src/shared/types.ts index 33a6933..8c5ae9b 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -1,19 +1,13 @@ -export type Message = { - session?: string - id?: string - type: MessageType - data?: CommandResult | CommandOutput -} +export type Message = + | ErrorMessage | InputMessage + | OutputMessage | SaveFileMessage | SessionStartMessage | SessionUpdateMessage | GameStartMessage - -export type MessageType = "error" | "input" | "output" | "commands" | "save-file" - | "game:start" - | "stream:start" | "stream:end" | "stream:append" | "stream:replace" - | "ui:mode" + | StreamMessage + | CommandsMessage export type CommandOutput = string | string[] | { text: string, script?: string } @@ -26,6 +20,22 @@ export type CommandResult = { output: CommandOutput } +export type ErrorMessage = { + type: "error" + data: string +} + +export type CommandsMessage = { + type: "commands" + data: string[] +} + +export type OutputMessage = { + type: "output" + id?: string + data: CommandResult +} + export type InputMessage = { type: "input" id: string @@ -56,6 +66,14 @@ export type SessionUpdateMessage = { } export type GameStartMessage = { + type: "game:start" id: string data: string +} + +export type StreamMessage = { + type: "stream:start" | "stream:end" | "stream:append" | "stream:replace" + id: string + session: string + data: CommandOutput } \ No newline at end of file diff --git a/src/stream.ts b/src/stream.ts index 177cfce..0269ba4 100644 --- a/src/stream.ts +++ b/src/stream.ts @@ -2,7 +2,7 @@ import { send as sendWs } from "./websocket" import { sessionGet } from "./session" import { processExecOutput } from "./shell" import type { Child } from "hono/jsx" -import type { CommandOutput, Message } from "./shared/types" +import type { CommandOutput, StreamMessage } from "./shared/types" type StreamFn = (output: Child) => Promise type StreamFns = { replace: StreamFn, append: StreamFn } @@ -16,7 +16,7 @@ export async function stream(initOrFn: StreamParamFn | string | any, fn?: Stream const taskId = state.taskId const session = state.sessionId - const send = (msg: Message) => { + const send = (msg: Omit) => { sendWs(state.ws, { ...msg, id: taskId, session }) }