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 { EventDetailHeader, EventViewer, type EventDetailAction } 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 }) => ( setHexDumps({ ...hexDumps, [index]: v })} showLarge={showLarge} showingLarge={showingLarge} setShowLarge={setShowLarge} setShowingLarge={setShowingLarge} /> )} /> ); } 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, }: { event: WebsocketEvent; hexDump: boolean; setHexDump: (v: boolean) => void; showLarge: boolean; showingLarge: boolean; setShowLarge: (v: boolean) => void; setShowingLarge: (v: boolean) => 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 ) : ( )}
); }