3Lens Debug Protocol
This document specifies the message protocol used for communication between the 3Lens Probe SDK and the DevTool UI.
Table of Contents
Overview
The 3Lens Debug Protocol is a bidirectional, JSON-based messaging protocol that enables:
- Probe → UI: Sending scene data, frame statistics, snapshots, and events
- UI → Probe: Sending commands to inspect, modify, or control the application
Design Goals
- Simplicity: JSON-based for easy debugging and tooling
- Efficiency: Support for batching, throttling, and delta updates
- Extensibility: Custom message types for plugins
- Transport-agnostic: Works over postMessage, WebSocket, or direct calls
Transport Mechanisms
1. postMessage (Browser Extension)
Used when the DevTool UI runs in a browser extension DevTools panel.
┌─────────────────────┐ ┌─────────────────────┐
│ Web Page │ │ DevTools Panel │
│ (Probe SDK) │ │ (UI Host) │
│ │ │ │
│ window.postMessage├───────────────────►│ window.onmessage │
│ │ │ │
│ window.onmessage │◄───────────────────┤ window.postMessage│
└─────────────────────┘ └─────────────────────┘2
3
4
5
6
7
8
Message Wrapper:
interface PostMessageWrapper {
source: '3lens-probe' | '3lens-devtool';
version: string;
payload: DebugMessage;
}2
3
4
5
2. WebSocket (Standalone App)
Used when connecting to a standalone desktop or web application.
┌─────────────────────┐ ┌─────────────────────┐
│ Web Page │ WebSocket │ Standalone App │
│ (Probe SDK) │◄──────────────────►│ (Electron/Web) │
│ │ ws://... │ │
└─────────────────────┘ └─────────────────────┘2
3
4
5
Connection URL: ws://localhost:3lens-port or custom
3. Direct Call (In-App Overlay)
Used when the overlay runs in the same runtime as the application.
// No serialization needed - direct function calls
probe.onMessage((msg) => overlay.handleMessage(msg));
overlay.onCommand((cmd) => probe.executeCommand(cmd));2
3
Message Format
Base Message Structure
All messages conform to this base structure:
interface DebugMessage {
/**
* Message type identifier
*/
type: string;
/**
* Unique message ID (for request/response correlation)
*/
id?: string;
/**
* Timestamp (milliseconds since epoch)
*/
timestamp: number;
/**
* Message payload (type-specific)
*/
[key: string]: unknown;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Message Categories
| Category | Direction | Description |
|---|---|---|
| Handshake | Bidirectional | Connection establishment |
| Data | Probe → UI | Scene data, stats, snapshots |
| Event | Probe → UI | Lifecycle and custom events |
| Command | UI → Probe | Control and inspection commands |
| Response | Bidirectional | Command/request responses |
Message Types
Handshake Messages
handshake-request (UI → Probe)
Sent by UI to initiate connection.
interface HandshakeRequest {
type: 'handshake-request';
id: string;
timestamp: number;
uiVersion: string;
capabilities: string[];
}2
3
4
5
6
7
handshake-response (Probe → UI)
Probe responds with application info.
interface HandshakeResponse {
type: 'handshake-response';
id: string;
timestamp: number;
requestId: string;
appId: string;
appName: string;
threeVersion: string;
probeVersion: string;
backend: 'webgl' | 'webgpu';
capabilities: string[];
config: {
sampling: SamplingConfig;
rules: RulesConfig;
tags: Record<string, string>;
};
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Data Messages
frame-stats (Probe → UI)
Per-frame statistics. Sent every frame or at configured intervals.
interface FrameStatsMessage {
type: 'frame-stats';
timestamp: number;
frame: number;
stats: FrameStats;
}2
3
4
5
6
7
Throttling: Can be throttled to every N frames or aggregated.
snapshot (Probe → UI)
Full scene snapshot.
interface SnapshotMessage {
type: 'snapshot';
id: string;
timestamp: number;
snapshotId: string;
trigger: 'manual' | 'on-change' | 'scheduled';
snapshot: SceneSnapshot;
}2
3
4
5
6
7
8
9
snapshot-delta (Probe → UI)
Incremental update to a previous snapshot.
interface SnapshotDeltaMessage {
type: 'snapshot-delta';
timestamp: number;
baseSnapshotId: string;
changes: SceneChange[];
}
interface SceneChange {
changeType: 'added' | 'removed' | 'updated';
path: string;
data?: SceneNode;
updates?: Partial<SceneNode>;
}2
3
4
5
6
7
8
9
10
11
12
13
14
materials-list (Probe → UI)
List of all materials.
interface MaterialsListMessage {
type: 'materials-list';
timestamp: number;
materials: MaterialInfo[];
}2
3
4
5
6
textures-list (Probe → UI)
List of all textures.
interface TexturesListMessage {
type: 'textures-list';
timestamp: number;
textures: TextureInfo[];
}2
3
4
5
6
render-targets-list (Probe → UI)
List of render targets with optional thumbnail data.
interface RenderTargetsListMessage {
type: 'render-targets-list';
timestamp: number;
renderTargets: RenderTargetInfo[];
thumbnails?: Record<string, string>; // ref -> base64 image
}2
3
4
5
6
7
geometries-list (Probe → UI)
List of all geometries.
interface GeometriesListMessage {
type: 'geometries-list';
timestamp: number;
geometries: GeometryInfo[];
}2
3
4
5
6
programs-list (Probe → UI, WebGL only)
List of shader programs.
interface ProgramsListMessage {
type: 'programs-list';
timestamp: number;
programs: ProgramInfo[];
}2
3
4
5
6
pipelines-list (Probe → UI, WebGPU only)
List of render/compute pipelines.
interface PipelinesListMessage {
type: 'pipelines-list';
timestamp: number;
pipelines: PipelineInfo[];
}2
3
4
5
6
Event Messages
resource-event (Probe → UI)
Resource lifecycle event.
interface ResourceEventMessage {
type: 'resource-event';
timestamp: number;
event: ResourceEvent;
}2
3
4
5
6
selection-changed (Probe → UI)
Object selection changed.
interface SelectionChangedMessage {
type: 'selection-changed';
timestamp: number;
selectedObject: ObjectMeta | null;
previousObject: ObjectMeta | null;
}2
3
4
5
6
7
scene-changed (Probe → UI)
Scene graph structure changed.
interface SceneChangedMessage {
type: 'scene-changed';
timestamp: number;
changeType: 'object-added' | 'object-removed' | 'hierarchy-changed';
affectedObjects: TrackedObjectRef[];
}2
3
4
5
6
7
rule-violation (Probe → UI)
Performance rule violated.
interface RuleViolationMessage {
type: 'rule-violation';
timestamp: number;
violation: RuleViolation;
}2
3
4
5
6
custom-metric (Probe → UI)
Custom metric recorded.
interface CustomMetricMessage {
type: 'custom-metric';
timestamp: number;
name: string;
value: number;
tags?: Record<string, string>;
}2
3
4
5
6
7
8
custom-event (Probe → UI)
Custom application event.
interface CustomEventMessage {
type: 'custom-event';
timestamp: number;
name: string;
data?: Record<string, unknown>;
}2
3
4
5
6
7
logical-entity-registered (Probe → UI)
New logical entity registered.
interface LogicalEntityRegisteredMessage {
type: 'logical-entity-registered';
timestamp: number;
entity: LogicalEntity;
}2
3
4
5
6
logical-entity-unregistered (Probe → UI)
Logical entity removed.
interface LogicalEntityUnregisteredMessage {
type: 'logical-entity-unregistered';
timestamp: number;
entityId: string;
}2
3
4
5
6
Command Messages (UI → Probe)
request-snapshot
Request a scene snapshot.
interface RequestSnapshotCommand {
type: 'request-snapshot';
id: string;
timestamp: number;
includeGeometryDetails?: boolean;
includeMaterialDetails?: boolean;
}2
3
4
5
6
7
8
request-materials
Request materials list.
interface RequestMaterialsCommand {
type: 'request-materials';
id: string;
timestamp: number;
}2
3
4
5
request-textures
Request textures list.
interface RequestTexturesCommand {
type: 'request-textures';
id: string;
timestamp: number;
}2
3
4
5
request-render-targets
Request render targets with optional thumbnails.
interface RequestRenderTargetsCommand {
type: 'request-render-targets';
id: string;
timestamp: number;
includeThumbnails?: boolean;
thumbnailSize?: number;
}2
3
4
5
6
7
8
select-object
Select an object by debugId.
interface SelectObjectCommand {
type: 'select-object';
id: string;
timestamp: number;
debugId: string | null;
}2
3
4
5
6
7
highlight-object
Temporarily highlight an object.
interface HighlightObjectCommand {
type: 'highlight-object';
id: string;
timestamp: number;
debugId: string;
duration?: number; // ms, default 2000
}2
3
4
5
6
7
8
isolate-object
Hide all objects except the specified one.
interface IsolateObjectCommand {
type: 'isolate-object';
id: string;
timestamp: number;
debugId: string;
}2
3
4
5
6
7
show-all-objects
Show all objects (undo isolation).
interface ShowAllObjectsCommand {
type: 'show-all-objects';
id: string;
timestamp: number;
}2
3
4
5
focus-camera
Move camera to focus on an object.
interface FocusCameraCommand {
type: 'focus-camera';
id: string;
timestamp: number;
debugId: string;
padding?: number;
}2
3
4
5
6
7
8
set-object-visibility
Set object visibility.
interface SetObjectVisibilityCommand {
type: 'set-object-visibility';
id: string;
timestamp: number;
debugId: string;
visible: boolean;
}2
3
4
5
6
7
8
set-object-transform
Set object transform.
interface SetObjectTransformCommand {
type: 'set-object-transform';
id: string;
timestamp: number;
debugId: string;
position?: Vector3Data;
rotation?: EulerData;
scale?: Vector3Data;
}2
3
4
5
6
7
8
9
10
set-material-property
Set a material property.
interface SetMaterialPropertyCommand {
type: 'set-material-property';
id: string;
timestamp: number;
materialRef: string;
property: string;
value: unknown;
}2
3
4
5
6
7
8
9
toggle-wireframe
Toggle wireframe mode for an object or material.
interface ToggleWireframeCommand {
type: 'toggle-wireframe';
id: string;
timestamp: number;
target: 'object' | 'material' | 'global';
debugId?: string;
materialRef?: string;
enabled?: boolean; // toggle if undefined
}2
3
4
5
6
7
8
9
10
toggle-bounding-box
Toggle bounding box display.
interface ToggleBoundingBoxCommand {
type: 'toggle-bounding-box';
id: string;
timestamp: number;
debugId: string;
enabled?: boolean;
}2
3
4
5
6
7
8
start-recording
Start recording session.
interface StartRecordingCommand {
type: 'start-recording';
id: string;
timestamp: number;
options?: {
captureFrameStats?: boolean;
captureSnapshots?: boolean;
captureEvents?: boolean;
maxDuration?: number; // ms
};
}2
3
4
5
6
7
8
9
10
11
12
stop-recording
Stop recording session.
interface StopRecordingCommand {
type: 'stop-recording';
id: string;
timestamp: number;
}2
3
4
5
export-recording
Export recorded session.
interface ExportRecordingCommand {
type: 'export-recording';
id: string;
timestamp: number;
format: 'json' | 'binary';
}2
3
4
5
6
7
set-sampling-config
Update sampling configuration.
interface SetSamplingConfigCommand {
type: 'set-sampling-config';
id: string;
timestamp: number;
config: Partial<SamplingConfig>;
}2
3
4
5
6
7
ping
Connection health check.
interface PingCommand {
type: 'ping';
id: string;
timestamp: number;
}2
3
4
5
Response Messages
command-response
Response to a command.
interface CommandResponse {
type: 'command-response';
id: string;
timestamp: number;
requestId: string;
success: boolean;
data?: unknown;
error?: {
code: string;
message: string;
};
}2
3
4
5
6
7
8
9
10
11
12
13
pong
Response to ping.
interface PongMessage {
type: 'pong';
id: string;
timestamp: number;
requestId: string;
}2
3
4
5
6
7
Connection Lifecycle
Connection Establishment
UI Probe
│ │
├───── handshake-request ───────────►│
│ │
│◄──── handshake-response ───────────┤
│ │
│◄──── frame-stats ──────────────────┤
│◄──── snapshot ─────────────────────┤
│ │
├───── select-object ───────────────►│
│◄──── selection-changed ────────────┤
│ │2
3
4
5
6
7
8
9
10
11
12
Heartbeat
// UI sends ping every 5 seconds
const HEARTBEAT_INTERVAL = 5000;
setInterval(() => {
transport.send({
type: 'ping',
id: generateId(),
timestamp: Date.now(),
});
}, HEARTBEAT_INTERVAL);2
3
4
5
6
7
8
9
10
Reconnection
- UI detects connection loss (no pong for 3 heartbeats)
- UI attempts reconnection with exponential backoff
- On reconnect, UI sends new handshake-request
- Probe responds with current state
Error Handling
Error Codes
| Code | Description |
|---|---|
OBJECT_NOT_FOUND | Referenced object doesn't exist |
MATERIAL_NOT_FOUND | Referenced material doesn't exist |
INVALID_PROPERTY | Property name is invalid |
READONLY_PROPERTY | Property cannot be modified |
INVALID_VALUE | Value type/range is invalid |
OPERATION_FAILED | Generic operation failure |
NOT_SUPPORTED | Operation not supported in current mode |
RATE_LIMITED | Too many requests |
Error Response Example
{
type: 'command-response',
id: 'resp-456',
timestamp: 1702912000000,
requestId: 'cmd-123',
success: false,
error: {
code: 'OBJECT_NOT_FOUND',
message: 'Object with debugId "mesh-xyz" not found in scene'
}
}2
3
4
5
6
7
8
9
10
11
Message Batching
For performance, multiple messages can be batched:
interface BatchMessage {
type: 'batch';
timestamp: number;
messages: DebugMessage[];
}2
3
4
5
The receiver should process messages in order.
Throttling & Rate Limiting
Frame Stats Throttling
// Probe configuration
{
sampling: {
frameStats: 10 // send every 10th frame
}
}2
3
4
5
6
Request Rate Limiting
- Maximum 100 commands per second
- Probe may respond with
RATE_LIMITEDerror
Version Compatibility
Protocol Version
The protocol version follows semver:
- Major: Breaking changes
- Minor: New message types (backward compatible)
- Patch: Bug fixes
Version Negotiation
During handshake, both parties exchange versions. The connection uses the lowest common version.
// UI supports versions 1.0.0 - 1.2.0
// Probe is version 1.1.0
// Connection uses 1.1.0 features2
3
Custom Messages
Plugins can define custom message types with a namespace prefix:
interface CustomPluginMessage {
type: 'plugin:lod-checker:analysis-result';
timestamp: number;
// Plugin-specific data
issues: LodIssue[];
}2
3
4
5
6
7
Convention: plugin:<plugin-id>:<message-name>