5.3 KiB
5.3 KiB
Terminal Module Documentation
Purpose
This module provides WebSocket protocol utilities for terminal input handling in the web server runtime, including message normalization, control frame parsing, rate limiting, and pathname resolution for terminal WebSocket connections.
Entrypoints and structure
packages/web/server/lib/terminal/: Terminal module directory.index.js: Stable module entrypoint that re-exports protocol helpers/constants.input-ws-protocol.js: Single-file module containing all terminal input WebSocket protocol utilities.
packages/web/server/lib/terminal/input-ws-protocol.test.js: Test file for protocol utilities.
Public API entry point: imported by packages/web/server/index.js from ./lib/terminal/index.js.
Public exports
Constants
TERMINAL_INPUT_WS_PATH: WebSocket endpoint path (/api/terminal/input-ws).TERMINAL_INPUT_WS_CONTROL_TAG_JSON: Control frame tag byte (0x01) indicating JSON payload.TERMINAL_INPUT_WS_MAX_PAYLOAD_BYTES: Maximum payload size (64KB).
Request Parsing
parseRequestPathname(requestUrl): Extracts pathname from request URL string. Returns empty string for invalid inputs.
Message Normalization
normalizeTerminalInputWsMessageToBuffer(rawData): Normalizes various data types (Buffer, Uint8Array, ArrayBuffer, string, chunk arrays) to a single Buffer.normalizeTerminalInputWsMessageToText(rawData): Normalizes data to UTF-8 text string. Passes through strings directly, converts binary data to text.
Control Frame Handling
readTerminalInputWsControlFrame(rawData): Parses WebSocket message as control frame. Returns parsed JSON object or null if invalid/malformed. Validates control tag prefix and JSON structure.createTerminalInputWsControlFrame(payload): Creates a control frame with JSON payload. Prepends control tag byte.
Rate Limiting
pruneRebindTimestamps(timestamps, now, windowMs): Filters timestamps to keep only those within the active time window.isRebindRateLimited(timestamps, maxPerWindow): Checks if rebind operations have exceeded rate limit threshold.
Response contracts
Control Frame
Control frames use binary encoding:
- First byte:
TERMINAL_INPUT_WS_CONTROL_TAG_JSON(0x01) - Remaining bytes: UTF-8 encoded JSON object
- Parsed result: Object or null on parse failure
Normalized Buffer
Input types are normalized to Buffer:
Buffer: Returned as-isUint8Array/ArrayBuffer: Converted to BufferString: Converted to UTF-8 BufferArray<Buffer|string|Uint8Array>: Concatenated to single Buffer
Rate Limiting
Rate limiting uses timestamp arrays:
pruneRebindTimestamps: Returns filtered array of active timestampsisRebindRateLimited: Returns boolean indicating if limit is reached
Usage in web server
The terminal protocol utilities are used by packages/web/server/index.js for:
- WebSocket endpoint path definition (
TERMINAL_INPUT_WS_PATH) - Message normalization for input handling
- Control frame parsing for session binding
- Rate limiting for session rebind operations
- Request pathname parsing for WebSocket routing
The web server uses these utilities in combination with bun-pty or node-pty for PTY session management.
Notes for contributors
Adding New Control Frame Types
- Define new control tag constants (e.g.,
TERMINAL_INPUT_WS_CONTROL_TAG_CUSTOM = 0x02) - Update
readTerminalInputWsControlFrameto handle new tag type - Update
createTerminalInputWsControlFrameor create new frame creation function - Add corresponding tests in
terminal-input-ws-protocol.test.js
Message Normalization
- Always normalize incoming WebSocket messages before processing
- Use
normalizeTerminalInputWsMessageToBufferfor binary data - Use
normalizeTerminalInputWsMessageToTextfor text data (terminal escape sequences) - Normalize chunked messages from WebSocket fragmentation handling
Rate Limiting
- Rate limiting is time-window based: tracks timestamps within a rolling window
- Use
pruneRebindTimestampsto clean up stale timestamps before rate limit checks - Configure
maxPerWindowbased on operational requirements (prevent abuse)
Error Handling
readTerminalInputWsControlFramereturns null for invalid/malformed framesparseRequestPathnamereturns empty string for invalid URLs- Callers should handle null/empty returns gracefully
Testing
- Run
bun run type-check,bun run lint, andbun run buildbefore finalizing changes - Test edge cases: empty payloads, malformed JSON, chunked messages, rate limit boundaries
- Verify control frame roundtrip: create → read → validate payload equality
- Test pathname parsing with relative URLs, absolute URLs, and invalid inputs
Verification notes
Manual verification
- Start web server and create terminal session via
/api/terminal/create - Connect to
/api/terminal/input-wsWebSocket - Send control frames with valid/invalid payloads to verify parsing
- Test message normalization with various data types
- Verify rate limiting by issuing rapid rebind requests
Automated verification
- Run test file:
bun test packages/web/server/lib/terminal/input-ws-protocol.test.js - Protocol tests should pass covering:
- WebSocket path constant
- Control frame encoding/decoding
- Payload validation
- Message normalization (all data types)
- Pathname parsing
- Rate limiting logic