# How the Floating Simulate Panel in law-chain-hot/websocket-devtools Enables WebSocket Message Editing and Simulation

> Master WebSocket message editing and simulation with the floating panel in law-chain-hot/websocket-devtools. Compose, persist, and send messages effortlessly with this powerful React component.

- Repository: [Brian 阿布/websocket-devtools](https://github.com/law-chain-hot/websocket-devtools)
- Tags: how-to-guide
- Published: 2026-03-05

---

**The floating simulate panel provides a draggable, resizable React component that lets developers compose JSON payloads, persist editor state across sessions, and send messages as either incoming or outgoing WebSocket traffic through an imperative `openPanel` API.**

The `law-chain-hot/websocket-devtools` repository is a browser extension that adds debugging capabilities to WebSocket connections. At its core, the **floating simulate panel** delivers an in-page sandbox where developers can craft, test, and reuse WebSocket messages without leaving the inspected tab. This self-contained UI widget combines a JSON editor, persistence layer, and simulation controls into a movable window that integrates seamlessly with the Chrome DevTools workflow.

## Architecture of the Floating Simulate Panel

The panel follows a layered React architecture that separates UI presentation from state management and animation logic.

### Component Hierarchy

The implementation spans four primary files in the `src` directory:

- **[`FloatingSimulate.jsx`](https://github.com/law-chain-hot/websocket-devtools/blob/main/FloatingSimulate.jsx)** – A wrapper component that uses `forwardRef` and `useImperativeHandle` to expose an `openPanel` method to parent components
- **[`SimulateMessagePanel.jsx`](https://github.com/law-chain-hot/websocket-devtools/blob/main/SimulateMessagePanel.jsx)** – The core container handling the draggable window, tab navigation, and message transmission logic
- **[`SimulateEditorTab.jsx`](https://github.com/law-chain-hot/websocket-devtools/blob/main/SimulateEditorTab.jsx)** – The editing interface featuring a `JsonViewer` component and simulation trigger buttons
- **[`usePanelManager.js`](https://github.com/law-chain-hot/websocket-devtools/blob/main/usePanelManager.js)** – A custom hook encapsulating open/toggle/minimize logic and `localStorage` synchronization

### State Management with usePanelManager

The `usePanelManager` hook centralizes panel visibility and geometry state. When `openPanel` is invoked, it retrieves persisted dimensions from `localStorage["simulateMessagePanel"]`, validates coordinates against viewport boundaries using `useWindowConstraints`, and triggers the opening animation.

```javascript
// src/hooks/usePanelManager.js
const openPanel = useCallback(() => {
  if (isWindowOpen || isAnimating) return;
  const savedState = localStorage.getItem("simulateMessagePanel");
  // Extract position & size, validate with useWindowConstraints
  setIsWindowOpen(true);
  animateWindowOpen(validatedPos);
}, [isWindowOpen, isAnimating, /* ... */]);

```

## Editing WebSocket Messages

The editing workflow centers on the **SimulateEditorTab**, which provides a structured environment for payload composition.

### The SimulateEditorTab Component

Located at [`src/components/SimulateEditorTab.jsx`](https://github.com/law-chain-hot/websocket-devtools/blob/main/src/components/SimulateEditorTab.jsx), this component renders a `JsonViewer` instance where developers can type or paste JSON payloads. The editor supports both structured data and plain text, storing the current value in the `simulateMessage` state variable. Two **Simulate** buttons (outgoing and incoming) flank the editor, allowing users to designate message direction before transmission.

### Persisting Editor State

State persistence occurs through a debounced save mechanism in [`SimulateMessagePanel.jsx`](https://github.com/law-chain-hot/websocket-devtools/blob/main/SimulateMessagePanel.jsx). On every change to `simulateMessage`, `isPinned`, `windowPosition`, or `windowSize`, the hook writes to `localStorage["simulateMessagePanel"]`. When the component mounts, it rehydrates the editor content and window geometry, restoring the previous editing session automatically.

## Simulating Incoming and Outgoing Messages

Simulation transforms the edited payload into actual WebSocket traffic routed through the background script.

### The handleSimulateMessage Flow

The `handleSimulateMessage` callback in [`SimulateMessagePanel.jsx`](https://github.com/law-chain-hot/websocket-devtools/blob/main/SimulateMessagePanel.jsx) validates the connection and payload before dispatching:

```javascript
// src/components/SimulateMessagePanel.jsx
const handleSimulateMessage = useCallback(
  async (direction, data = null) => {
    const messageData = data || simulateMessage;
    if (!connection || !messageData.trim() || isSending) return;
    
    setIsSending(true);
    await onSimulateMessage({
      connectionId: connection.id,
      message: messageData,
      direction, // 'outgoing' or 'incoming'
    });
    setTimeout(() => setIsSending(false), 200);
  },
  [connection, simulateMessage, isSending, onSimulateMessage]
);

```

The `direction` parameter determines whether the message appears as client-sent or server-received traffic in the WebSocket inspector.

### Programmatic Panel Control

Parent components can pre-populate the editor and open the panel imperatively using the exposed `ref` API:

```jsx
import React, { useRef } from "react";
import FloatingSimulate from "./src/components/FloatingSimulate";

function DebugView({ connection }) {
  const simulateRef = useRef(null);

  const testConnection = () => {
    simulateRef.current?.openPanel({
      tab: "editor",
      data: JSON.stringify({ type: "ping", timestamp: Date.now() }, null, 2),
    });
  };

  return (
    <>
      <button onClick={testConnection}>Send Test Ping</button>
      <FloatingSimulate
        ref={simulateRef}
        connection={connection}
        onSimulateMessage={async ({ connectionId, message, direction }) => {
          await chrome.runtime.sendMessage({
            type: "simulate-message",
            payload: { connectionId, message, direction },
          });
        }}
      />
    </>
  );
}

```

## Draggable UI and Persistence

The panel leverages `react-rnd` to provide desktop-application-like windowing behavior within the browser viewport.

### Window Constraints and Animation

The `useWindowConstraints` hook ensures the panel never leaves the visible screen area during drag operations, while `useWindowAnimation` provides smooth entrance transitions when `openPanel` triggers. Resize events update the `windowSize` state, which persists alongside position coordinates via the debounced save routine.

### Saving Favorites and System Events

Beyond basic editing, the panel includes auxiliary tabs for workflow efficiency:

- **Favorites**: Uses [`globalFavorites.js`](https://github.com/law-chain-hot/websocket-devtools/blob/main/globalFavorites.js) and `favoritesService` to store reusable message snippets. Users can save the current editor content to favorites via `globalFavorites.addFromEditor`.
- **System Events**: Provides pre-defined WebSocket events (close, error, ping) that dispatch through `chrome.runtime.sendMessage` to simulate connection-level scenarios without manual JSON composition.

The **pin** functionality (`isPinned` state) keeps the panel visible when clicking outside the window, while the minimize button (`setIsWindowOpen(false)`) hides the panel to a floating toggle button without destroying state.

## Summary

- The **floating simulate panel** exposes an imperative `openPanel` API via [`FloatingSimulate.jsx`](https://github.com/law-chain-hot/websocket-devtools/blob/main/FloatingSimulate.jsx) for programmatic control from parent components.
- **[`SimulateEditorTab.jsx`](https://github.com/law-chain-hot/websocket-devtools/blob/main/SimulateEditorTab.jsx)** provides the JSON editing interface, while **[`SimulateMessagePanel.jsx`](https://github.com/law-chain-hot/websocket-devtools/blob/main/SimulateMessagePanel.jsx)** handles message transmission and state persistence.
- The `handleSimulateMessage` function supports bidirectional simulation, sending payloads as either incoming or outgoing WebSocket traffic.
- **Window state** (size, position, pin status) persists to `localStorage["simulateMessagePanel"]` via debounced writes in `usePanelManager`.
- **Favorites** and **System Events** tabs streamline reuse of common payloads and connection events.

## Frequently Asked Questions

### How do I open the floating simulate panel programmatically with pre-filled data?

Acquire a `ref` to the `FloatingSimulate` component and call `ref.current.openPanel({ tab: "editor", data: yourJSONString })`. This API is exposed through `useImperativeHandle` in [`src/components/FloatingSimulate.jsx`](https://github.com/law-chain-hot/websocket-devtools/blob/main/src/components/FloatingSimulate.jsx), allowing host components to trigger the panel and populate the editor in a single command.

### Where is the panel position and editor content saved between sessions?

The panel persists state to `localStorage` under the key `"simulateMessagePanel"`. The `usePanelManager` hook writes window size, position, pin status, and current message content via a debounced save mechanism, then rehydrates these values when the component mounts.

### Can the panel simulate both client-to-server and server-to-client messages?

Yes. The `handleSimulateMessage` function accepts a `direction` parameter (`"outgoing"` or `"incoming"`) that determines how the message appears in the WebSocket traffic log. Both directions execute through the same validation and transmission pipeline in [`SimulateMessagePanel.jsx`](https://github.com/law-chain-hot/websocket-devtools/blob/main/SimulateMessagePanel.jsx).

### What prevents the draggable panel from disappearing off-screen?

The `useWindowConstraints` hook validates coordinates against the current viewport dimensions before and during drag operations. When `openPanel` restores saved geometry from `localStorage`, it recalculates position to ensure the panel remains fully visible even if the browser window has resized since the last session.