import type { WebsocketEvent, WebsocketRequest } from "@yaakapp-internal/models"; import { hexy } from "hexy"; import { useAtomValue } from "jotai"; import { useMemo, useState } from "react"; import { useFormatText } from "../hooks/useFormatText"; import { activeWebsocketConnectionAtom, activeWebsocketConnectionsAtom, setPinnedWebsocketConnectionId, useWebsocketEvents, } from "../hooks/usePinnedWebsocketConnection"; import { useStateWithDeps } from "../hooks/useStateWithDeps"; import { languageFromContentType } from "../lib/contentType"; import { Button } from "./core/Button"; import { Editor } from "./core/Editor/LazyEditor"; import { type EventDetailAction, EventDetailHeader, EventViewer } from "./core/EventViewer"; import { EventViewerRow } from "./core/EventViewerRow"; import { HotkeyList } from "./core/HotkeyList"; import { Icon } from "./core/Icon"; import { LoadingIcon } from "./core/LoadingIcon"; import { HStack, VStack } from "./core/Stacks"; import { WebsocketStatusTag } from "./core/WebsocketStatusTag"; import { EmptyStateText } from "./EmptyStateText"; import { ErrorBoundary } from "./ErrorBoundary"; import { RecentWebsocketConnectionsDropdown } from "./RecentWebsocketConnectionsDropdown"; interface Props { activeRequest: WebsocketRequest; } export function WebsocketResponsePane({ activeRequest }: Props) { const [showLarge, setShowLarge] = useStateWithDeps(false, [activeRequest.id]); const [showingLarge, setShowingLarge] = useState(false); const [hexDumps, setHexDumps] = useState>({}); const activeConnection = useAtomValue(activeWebsocketConnectionAtom); const connections = useAtomValue(activeWebsocketConnectionsAtom); const events = useWebsocketEvents(activeConnection?.id ?? null); if (activeConnection == null) { return ( ); } const header = ( {activeConnection.state !== "closed" && ( )} {events.length} Messages ); return ( event.id} error={activeConnection.error} header={header} splitLayoutName="websocket_events" defaultRatio={0.4} renderRow={({ event, isActive, onClick }) => ( )} renderDetail={({ event, index, onClose }) => ( setHexDumps({ ...hexDumps, [index]: v })} showLarge={showLarge} showingLarge={showingLarge} setShowLarge={setShowLarge} setShowingLarge={setShowingLarge} onClose={onClose} /> )} /> ); } function WebsocketEventRow({ event, isActive, onClick, }: { event: WebsocketEvent; isActive: boolean; onClick: () => void; }) { const { message: messageBytes, isServer, messageType } = event; const message = messageBytes ? new TextDecoder("utf-8").decode(Uint8Array.from(messageBytes)) : ""; const iconColor = messageType === "close" || messageType === "open" ? "secondary" : isServer ? "info" : "primary"; const icon = messageType === "close" || messageType === "open" ? "info" : isServer ? "arrow_big_down_dash" : "arrow_big_up_dash"; const content = messageType === "close" ? ( "Disconnected from server" ) : messageType === "open" ? ( "Connected to server" ) : message === "" ? ( No content ) : ( {message.slice(0, 1000)} ); return ( } content={content} timestamp={event.createdAt} /> ); } function WebsocketEventDetail({ event, hexDump, setHexDump, showLarge, showingLarge, setShowLarge, setShowingLarge, onClose, }: { event: WebsocketEvent; hexDump: boolean; setHexDump: (v: boolean) => void; showLarge: boolean; showingLarge: boolean; setShowLarge: (v: boolean) => void; setShowingLarge: (v: boolean) => void; onClose: () => void; }) { const message = useMemo(() => { if (hexDump) { return event.message ? hexy(event.message) : ""; } return event.message ? new TextDecoder("utf-8").decode(Uint8Array.from(event.message)) : ""; }, [event.message, hexDump]); const language = languageFromContentType(null, message); const formattedMessage = useFormatText({ language, text: message, pretty: true }); const title = event.messageType === "close" ? "Connection Closed" : event.messageType === "open" ? "Connection Open" : `Message ${event.isServer ? "Received" : "Sent"}`; const actions: EventDetailAction[] = message !== "" ? [ { key: "toggle-hexdump", label: hexDump ? "Show Message" : "Show Hexdump", onClick: () => setHexDump(!hexDump), }, ] : []; return (
{!showLarge && event.message.length > 1000 * 1000 ? ( Message previews larger than 1MB are hidden
) : event.message.length === 0 ? ( No Content ) : ( )}
); }