narrow Message types

This commit is contained in:
Chris Wanstrath 2025-10-03 07:00:25 -07:00
parent 24a17f2b46
commit 2b66107866
10 changed files with 59 additions and 36 deletions

View File

@ -11,13 +11,15 @@ export async function dispatchMessage(ws: any, msg: Message) {
console.log("<- receive", msg) console.log("<- receive", msg)
switch (msg.type) { switch (msg.type) {
case "input": case "input":
await inputMessage(ws, msg as InputMessage); break await inputMessage(ws, msg); break
case "save-file": case "save-file":
await saveFileMessage(ws, msg as SaveFileMessage); break await saveFileMessage(ws, msg); break
case "ui:mode": case "session:update":
setState("ui:mode", msg.data); break for (const key of Object.keys(msg.data))
setState(key, msg.data[key]);
break
default: default:
send(ws, { type: "error", data: `unknown message: ${msg.type}` }) send(ws, { type: "error", data: `unknown message: ${msg.type}` })

View File

@ -58,7 +58,6 @@ function closeBrowser() {
iframe.style.pointerEvents = "none" iframe.style.pointerEvents = "none"
iframe.tabIndex = -1 iframe.tabIndex = -1
iframe.classList.remove("fullscreen", "active") iframe.classList.remove("fullscreen", "active")
iframe.style.border = "2px solid var(--c64-light-blue)"
scrollback.append(iframe) scrollback.append(iframe)
controls.style.display = "none" controls.style.display = "none"

View File

@ -22,7 +22,7 @@ export const browserCommands: Record<string, (...args: string[]) => void | Promi
mode: (mode?: string) => { mode: (mode?: string) => {
if (!mode) { if (!mode) {
mode = document.body.dataset.mode === "tall" ? "cinema" : "tall" 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 = "" content.style.display = ""

View File

@ -1,3 +1,4 @@
import type { SaveFileMessage } from "../shared/types"
import { scrollback } from "./dom" import { scrollback } from "./dom"
import { send } from "./websocket" import { send } from "./websocket"
import { focusInput } from "./focus" import { focusInput } from "./focus"
@ -49,10 +50,10 @@ function keydownHandler(e: KeyboardEvent) {
} else if ((e.ctrlKey && e.key === "s") || (e.ctrlKey && e.key === "Enter")) { } else if ((e.ctrlKey && e.key === "s") || (e.ctrlKey && e.key === "Enter")) {
e.preventDefault() e.preventDefault()
send({ send({
id: editor.dataset.path, id: editor.dataset.path || "/tmp.txt",
type: "save-file", type: "save-file",
data: editor.value data: editor.value
}) } as SaveFileMessage)
} else if (e.key === "{") { } else if (e.key === "{") {
if (editor.selectionStart !== editor.selectionEnd) { if (editor.selectionStart !== editor.selectionEnd) {
insertAroundSelection(editor, '{', '}') insertAroundSelection(editor, '{', '}')

View File

@ -133,6 +133,7 @@ function handleInputClick(e: MouseEvent) {
export function handleOutput(msg: Message) { export function handleOutput(msg: Message) {
const result = msg.data as CommandResult const result = msg.data as CommandResult
setStatus(msg.id!, result.status) const id = "id" in msg ? msg.id || "" : ""
addOutput(msg.id!, result.output) setStatus(id, result.status)
addOutput(id, result.output)
} }

View File

@ -1,6 +1,7 @@
//// ////
// The shell runs on the server and processes input, returning output. // The shell runs on the server and processes input, returning output.
import type { InputMessage } from "../shared/types"
import { addInput, setStatus, addOutput } from "./scrollback" import { addInput, setStatus, addOutput } from "./scrollback"
import { send } from "./websocket" import { send } from "./websocket"
import { randomId } from "../shared/utils" import { randomId } from "../shared/utils"
@ -27,7 +28,7 @@ export async function runCommand(input: string) {
if (result) addOutput(id, result) if (result) addOutput(id, result)
setStatus(id, "ok") setStatus(id, "ok")
} else { } else {
send({ id, type: "input", data: input }) send({ id, type: "input", data: input } as InputMessage)
} }
} }

View File

@ -1,25 +1,25 @@
import type { Message, CommandOutput } from "@/shared/types" import type { StreamMessage } from "@/shared/types"
import { addOutput, appendOutput, replaceOutput } from "./scrollback" import { addOutput, appendOutput, replaceOutput } from "./scrollback"
export function handleStreamStart(msg: Message) { export function handleStreamStart(msg: StreamMessage) {
const id = msg.id! const id = msg.id
const status = document.querySelector(`[data-id="${id}"].input .status`) const status = document.querySelector(`[data-id="${id}"].input .status`)
if (!status) return if (!status) return
addOutput(id, msg.data as CommandOutput) addOutput(id, msg.data)
status.classList.remove("yellow") status.classList.remove("yellow")
status.classList.add("purple") status.classList.add("purple")
} }
export function handleStreamAppend(msg: Message) { export function handleStreamAppend(msg: StreamMessage) {
appendOutput(msg.id!, msg.data as CommandOutput) appendOutput(msg.id, msg.data)
} }
export function handleStreamReplace(msg: Message) { export function handleStreamReplace(msg: StreamMessage) {
replaceOutput(msg.id!, msg.data as CommandOutput) replaceOutput(msg.id, msg.data)
} }
export function handleStreamEnd(_msg: Message) { export function handleStreamEnd(_msg: StreamMessage) {
} }

View File

@ -9,7 +9,7 @@ import { addErrorMessage } from "./scrollback"
const MAX_RETRIES = 5 const MAX_RETRIES = 5
let retries = 0 let retries = 0
let connected = false let connected = false
let msgQueue: Message[] = [] let msgQueue: Omit<Message, "session">[] = []
let ws: WebSocket | null = null let ws: WebSocket | null = null
@ -30,14 +30,15 @@ export function startConnection() {
} }
// send any message // send any message
export function send(msg: Message) { export function send(msg: Omit<Message, "session">) {
if (!connected) { if (!connected) {
msgQueue.push(msg) msgQueue.push(msg)
startConnection() startConnection()
return 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)) ws?.readyState === 1 && ws.send(JSON.stringify(msg))
console.log("-> send", msg) console.log("-> send", msg)
} }

View File

@ -1,19 +1,13 @@
export type Message = { export type Message =
session?: string | ErrorMessage
id?: string
type: MessageType
data?: CommandResult | CommandOutput
}
| InputMessage | InputMessage
| OutputMessage
| SaveFileMessage | SaveFileMessage
| SessionStartMessage | SessionStartMessage
| SessionUpdateMessage | SessionUpdateMessage
| GameStartMessage | GameStartMessage
| StreamMessage
export type MessageType = "error" | "input" | "output" | "commands" | "save-file" | CommandsMessage
| "game:start"
| "stream:start" | "stream:end" | "stream:append" | "stream:replace"
| "ui:mode"
export type CommandOutput = string | string[] export type CommandOutput = string | string[]
| { text: string, script?: string } | { text: string, script?: string }
@ -26,6 +20,22 @@ export type CommandResult = {
output: CommandOutput 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 = { export type InputMessage = {
type: "input" type: "input"
id: string id: string
@ -56,6 +66,14 @@ export type SessionUpdateMessage = {
} }
export type GameStartMessage = { export type GameStartMessage = {
type: "game:start"
id: string id: string
data: string data: string
}
export type StreamMessage = {
type: "stream:start" | "stream:end" | "stream:append" | "stream:replace"
id: string
session: string
data: CommandOutput
} }

View File

@ -2,7 +2,7 @@ import { send as sendWs } from "./websocket"
import { sessionGet } from "./session" import { sessionGet } from "./session"
import { processExecOutput } from "./shell" import { processExecOutput } from "./shell"
import type { Child } from "hono/jsx" 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<void> type StreamFn = (output: Child) => Promise<void>
type StreamFns = { replace: StreamFn, append: StreamFn } 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 taskId = state.taskId
const session = state.sessionId const session = state.sessionId
const send = (msg: Message) => { const send = (msg: Omit<StreamMessage, "id" | "session">) => {
sendWs(state.ws, { ...msg, id: taskId, session }) sendWs(state.ws, { ...msg, id: taskId, session })
} }