Skip to content

DevTools

Teleportal includes built-in DevTools for debugging and monitoring your application. The DevTools provide real-time visibility into message flow, connection state, document activity, and system statistics.

The Teleportal DevTools helps you debug and monitor your Teleportal applications by providing:

  • Real-time message monitoring: Track all sent and received messages with detailed metadata
  • Connection state tracking: Monitor connection status, transport type, and errors
  • Message filtering: Filter messages by document, type, direction, and search text
  • Message inspection: View detailed payloads, metadata, and acknowledgment status
  • Statistics: Track message counts, rates, document counts, and message types

The UI is organized into three tabs – Messages, Documents, and Presence – plus an always-visible connection status area with a details popover:

  • Header bar: a tab bar (Messages / Documents / Presence) with count badges (document count, peer count), and a connection status indicator. Clicking the connection status opens a popover with live internals (in-flight/buffered counts, AIMD batch window, reconnect attempts, SharedWorker pooling details, and a timeline of state transitions).
  • Messages tab: a collapsible filters row (search, direction selector, document checkboxes, message type checkboxes, message limit input), a scrollable message list (left, newest first), and a message inspector (right). RPC request/stream/response messages collapse into a single call row with status, latency, and error details.
  • Documents tab: a tree of documents and subdocuments with live sync handshake state (sync-step-1 → sync-step-2 → synced), traffic counters, encryption, and last activity. Clicking a document filters the Messages tab.
  • Presence tab: a live peer roster derived from presence-join/leave/heartbeat messages, with expandable per-peer data and a recent join/leave feed.

The connection status indicator uses color coding: green for connected, yellow for connecting, gray for disconnected, and red for errored.

All settings – including filters, message limit, and panel state – persist in localStorage across sessions.

import { createTeleportalDevtools } from "teleportal/devtools";
// Create the devtools element
const devtoolsElement = createTeleportalDevtools();
// Append to your DOM
document.body.appendChild(devtoolsElement);
import { useState, useEffect, useRef } from "react";
import { createTeleportalDevtools, getDevtoolsState } from "teleportal/devtools";
export function TeleportalDevtoolsPanel() {
const containerRef = useRef<HTMLDivElement>(null);
const devtoolsRef = useRef<HTMLElement | null>(null);
const [state] = useState(() => getDevtoolsState());
useEffect(() => {
if (!containerRef.current) return;
const devtoolsElement = createTeleportalDevtools(state);
containerRef.current.appendChild(devtoolsElement);
devtoolsRef.current = devtoolsElement;
return () => {
if (devtoolsRef.current) {
const cleanup = (devtoolsRef.current as any).__teleportalDevtoolsCleanup;
if (cleanup) {
cleanup();
}
if (containerRef.current && devtoolsRef.current.parentNode === containerRef.current) {
containerRef.current.removeChild(devtoolsRef.current);
}
}
};
}, []);
return <div ref={containerRef} style={{ height: "100%", width: "100%" }} />;
}

In a real application you typically want the devtools to appear only during development and to be toggleable so it does not obscure the rest of the UI. The example below shows a bottom-drawer pattern with conditional rendering and proper cleanup.

src/components/DevtoolsDrawer.tsx
import { useState, useEffect, useRef, useCallback } from "react";
import { createTeleportalDevtools, getDevtoolsState } from "teleportal/devtools";
export function DevtoolsDrawer() {
const [open, setOpen] = useState(false);
const containerRef = useRef<HTMLDivElement>(null);
const devtoolsRef = useRef<HTMLElement | null>(null);
const [state] = useState(() => getDevtoolsState());
useEffect(() => {
if (!open || !containerRef.current) return;
const devtoolsElement = createTeleportalDevtools(state);
containerRef.current.appendChild(devtoolsElement);
devtoolsRef.current = devtoolsElement;
return () => {
if (devtoolsRef.current) {
const cleanup = (devtoolsRef.current as any).__teleportalDevtoolsCleanup;
if (cleanup) cleanup();
devtoolsRef.current.remove();
devtoolsRef.current = null;
}
};
}, [open]);
return (
<>
<button
onClick={() => setOpen((prev) => !prev)}
style={{
position: "fixed",
bottom: open ? 320 : 8,
right: 8,
zIndex: 10000,
}}
>
{open ? "Close DevTools" : "Open DevTools"}
</button>
{open && (
<div
ref={containerRef}
style={{
position: "fixed",
bottom: 0,
left: 0,
right: 0,
height: 300,
zIndex: 9999,
borderTop: "2px solid #333",
}}
/>
)}
</>
);
}
src/App.tsx
import { DevtoolsDrawer } from "./components/DevtoolsDrawer";
function App() {
return (
<>
{/* Your application content */}
{import.meta.env.DEV && <DevtoolsDrawer />}
</>
);
}

Key points:

  • import.meta.env.DEV is a Vite built-in that is true only during development, so the drawer is tree-shaken out of production builds.
  • The devtools element is created when the drawer opens and cleaned up when it closes, so resources are not held when the panel is hidden.
  • The __teleportalDevtoolsCleanup function unsubscribes all event listeners and clears internal state, preventing memory leaks.

The DevTools capture every message flowing through the Teleportal provider in real time. Each message is stored with metadata including its unique ID, direction (sent or received), timestamp, associated document, and the provider instance that handled it.

Key behaviors:

  • ACK tracking: ACK messages are hidden from the message list to reduce noise, but they are tracked separately. When an ACK is received, the original message it acknowledges is updated with an ACK indicator, and the inspector shows the ACK details (ACK message ID, acknowledged message ID, and timestamp) in an expandable section.
  • Message deduplication: Messages are deduplicated by their ID using an O(1) index lookup, so duplicate deliveries do not produce duplicate entries in the list.
  • Configurable message limit: The message limit (default: 200) caps how many messages are kept in memory. When the limit is exceeded, the oldest messages are removed and their contributions to the statistics are subtracted. You can change the limit at any time via the input in the filters header.
  • Message rate: The rate is calculated as the number of messages received or sent within the last 10 seconds, divided by 10, giving a smoothed messages-per-second value.
  • Per-document tracking: A DocumentTracker maintains a registry of all documents seen in messages, recording their message counts, last activity timestamps, and provider associations.

The DevTools recognize and color-code messages by the wire protocol’s message kinds – doc, awareness, presence, rpc, and ack. getMessageTypeLabel() derives the row label and getMessageTypeColor() its badge color.

Type Color
sync-step-1 Blue
sync-step-2 Darker blue
update Green
sync-done Darker green
auth-message Red
Type Color
awareness-update Yellow
awareness-request Darker yellow
Type Color
presence-join / presence-leave / presence-heartbeat / presence-announce Purple

The row label is the payload’s type; presence messages also feed the roster in the Presence tab.

RPC is the transport for milestones (listMilestones, …), the key registry, and file transfers (fileUpload / fileDownload). There is no standalone milestone-* or file-* message type – those are RPC methods carried over rpc messages. The badge is indigo, shaded by requestType:

Request type Label Color
request <rpcMethod> Darkest indigo (600)
response <rpcMethod> Medium indigo (500)
stream / part <rpcMethod> (part) Lightest indigo (400)

RPC messages sharing an originalRequestId are grouped into a single call row. File-transfer upload chunks never appear as messages – upload progress comes from the file protocol’s progress events instead.

Type Color
ack Gray (hidden from list but tracked)

The DevTools provide filtering controls in the collapsible filters panel. All filter settings persist in localStorage and are restored on page reload.

Multi-select checkboxes listing every document that has appeared in a message. Documents are auto-discovered as messages arrive – there is no manual configuration. Select one or more documents to show only their messages.

Checkboxes for each message type allow you to hide specific types from the list. This is useful for focusing on a particular category, for example hiding all awareness updates to concentrate on sync traffic.

A selector with three options:

  • All: Show both sent and received messages
  • Sent: Show only outgoing messages
  • Received: Show only incoming messages

A text input that searches across message payloads and document IDs. The search is case-insensitive and debounced by 300ms so that filtering does not block the UI during rapid typing.

The DevTools are especially useful for diagnosing common synchronization problems.

Look for a sync-step-1 message that was sent but no corresponding sync-step-2 received. This means the server did not respond to the initial sync request. Check whether an auth-message was received instead, which indicates a permission or authentication error.

If you see an unusually high message rate or the same document data arriving repeatedly, check the message IDs. The DevTools deduplicate by ID, so identical IDs will not appear twice. If many distinct IDs carry the same payload, the application may have an update loop – a common cause is subscribing to Y.js update events and re-broadcasting them without checking their origin.

Watch the connection status indicator in the filters header. If it cycles rapidly between connecting (yellow) and disconnected (gray), there is likely a network issue, a server-side rejection, or a misconfigured reconnection strategy. Check for auth-message types with error codes that might explain the rejection.

Filter to awareness messages only using the message type filter. Verify that awareness-request messages are being sent. If they are sent but no awareness-update is received in response, the server may not be forwarding awareness state, or the document ID in the request may not match any active document on the server.

File transfers run over RPC. Filter to rpc messages and look for the fileUpload / fileDownload call rows. A failed call shows its error code and details in the call row and inspector. Note that upload chunks never appear as messages – they stay off the event pipeline for throughput, so upload progress and completion come from the file protocol’s progress events rather than visible stream parts. Download parts, by contrast, arrive as received messages and are grouped under their call row.

Ensure the devtools element is created after the Provider is initialized. The EventManager subscribes to teleportalEventClient events at construction time. If the devtools are created before the provider emits events, the subscriptions will be in place but no events will flow until the provider connects.

Lower the message limit in the filters header. The default of 200 is conservative, but for high-traffic applications with many concurrent documents or frequent awareness updates, consider setting it to 50-100. Each stored message retains references to the original message object and the provider, so large payloads can add up.

The DevTools store settings in localStorage under the key teleportal-devtools-settings. If settings are not persisting, check whether localStorage is available in your environment. Private browsing modes, iframe sandbox restrictions, and certain browser extensions can block localStorage access.

Always call the cleanup function when the devtools element is removed from the DOM to prevent memory leaks from lingering event listeners:

const devtoolsElement = createTeleportalDevtools();
// Later, when cleaning up:
const cleanup = (devtoolsElement as any).__teleportalDevtoolsCleanup;
if (cleanup) {
cleanup();
}

This unsubscribes from all teleportalEventClient event listeners, clears internal state, and removes subscriber callbacks.

The DevTools integrate with the Teleportal event system through teleportalEventClient from teleportal/providers. The EventManager subscribes to the following events at construction time:

  • received-message – captures every incoming message with its provider and connection context
  • sent-message – captures every outgoing message
  • connected – updates the connection state indicator to connected
  • disconnected – updates the connection state indicator to disconnected
  • update – handles connection state transitions (connecting, errored, etc.)
  • load-subdoc – registers a new subdocument in the document tracker
  • unload-subdoc – removes a subdocument from the document tracker

Because these are global events emitted by the provider, the DevTools automatically capture traffic from all providers and connections active on the page.

  • Provider – Learn how providers work and how events flow
  • Protocol – Understand the message types the DevTools display
  • Performance – Optimize your Teleportal application