Replace ansiToHtml with terminal serialization (Phase 2)
The output handler now: - Writes raw data to terminal emulator - Stores raw data in DB (unchanged) - Serializes terminal state as HTML - Broadcasts serialized HTML via SSE This replaces the ansiToHtml() + splitAnsiCarryover() approach with proper terminal emulation. The terminal emulator handles all ANSI sequences internally, including incomplete sequences across chunks.
This commit is contained in:
parent
6c7de2332b
commit
88afb7249d
1 changed files with 22 additions and 25 deletions
|
|
@ -1,8 +1,6 @@
|
||||||
// Core server: HTTP + WebSocket + SSE
|
// Core server: HTTP + WebSocket + SSE
|
||||||
|
|
||||||
import type { ServerWebSocket } from "bun";
|
import type { ServerWebSocket } from "bun";
|
||||||
import { ansiToHtml } from "./ansi";
|
|
||||||
import { splitAnsiCarryover } from "./ansi-carryover";
|
|
||||||
import {
|
import {
|
||||||
appendOutput,
|
appendOutput,
|
||||||
createDevice,
|
createDevice,
|
||||||
|
|
@ -19,7 +17,12 @@ import {
|
||||||
updateLastSeen,
|
updateLastSeen,
|
||||||
updateSessionStats,
|
updateSessionStats,
|
||||||
} from "./db";
|
} from "./db";
|
||||||
import { createTerminal, disposeTerminal, type TerminalSession } from "./terminal";
|
import {
|
||||||
|
createTerminal,
|
||||||
|
disposeTerminal,
|
||||||
|
serializeAsHTML,
|
||||||
|
type TerminalSession,
|
||||||
|
} from "./terminal";
|
||||||
import type {
|
import type {
|
||||||
AnswerResponse,
|
AnswerResponse,
|
||||||
ClientMessage,
|
ClientMessage,
|
||||||
|
|
@ -494,35 +497,29 @@ const server = Bun.serve<SessionData>({
|
||||||
// Handle output message
|
// Handle output message
|
||||||
if (msg.type === "output") {
|
if (msg.type === "output") {
|
||||||
const sessionId = ws.data.sessionId;
|
const sessionId = ws.data.sessionId;
|
||||||
// Join with any saved carryover from previous chunk
|
|
||||||
const prevCarry = ansiCarryovers.get(sessionId) || "";
|
|
||||||
const combined = prevCarry ? prevCarry + msg.data : msg.data;
|
|
||||||
// Determine if new tail is an incomplete control sequence and split
|
|
||||||
const [body, carry] = splitAnsiCarryover(combined);
|
|
||||||
if (carry) {
|
|
||||||
console.debug(
|
|
||||||
`Session ${sessionId}: ANSI carryover detected (${carry.length} bytes)`,
|
|
||||||
);
|
|
||||||
ansiCarryovers.set(sessionId, carry);
|
|
||||||
} else if (prevCarry) {
|
|
||||||
console.debug(`Session ${sessionId}: ANSI carryover resolved`);
|
|
||||||
// Clear carry if previously set and now resolved
|
|
||||||
ansiCarryovers.delete(sessionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
appendOutput(sessionId, body); // Store raw ANSI without trailing incomplete fragment
|
|
||||||
|
|
||||||
// Write to terminal emulator
|
|
||||||
const termSession = sessionTerminals.get(sessionId);
|
const termSession = sessionTerminals.get(sessionId);
|
||||||
if (termSession) {
|
|
||||||
termSession.terminal.write(msg.data);
|
if (!termSession) {
|
||||||
|
console.error(`No terminal for session ${sessionId}`);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write to terminal emulator (handles all ANSI sequences)
|
||||||
|
termSession.terminal.write(msg.data);
|
||||||
|
|
||||||
|
// Store raw ANSI in database
|
||||||
|
appendOutput(sessionId, msg.data);
|
||||||
|
|
||||||
|
// Serialize current terminal state as HTML
|
||||||
|
const html = serializeAsHTML(termSession);
|
||||||
|
|
||||||
|
// Broadcast to dashboards
|
||||||
broadcastSSE({
|
broadcastSSE({
|
||||||
type: "output",
|
type: "output",
|
||||||
session_id: sessionId,
|
session_id: sessionId,
|
||||||
data: ansiToHtml(body), // Parse for display
|
data: html,
|
||||||
});
|
});
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue