TomatoRTC

Signals, awareness, and messaging — without one pipeline doing everything.

Four first-class primitives on one shared WebSocket. Pick the right tool — don't overload chat with cursors or awareness with messages.

SharedSignals Awareness Chat + receipts Data channels
SharedSignal

Fire-and-forget events — cursors, reactions, strokes. Not stored.

Awareness

Last-write-wins per user — status, tool, viewport. Snapshot on join.

Chat

Text messages with delivery/read receipts and optional durable backends.

Data channel

Peer or SFU-backed binary payloads — files, game state, custom sync.

Choose the right primitive

Four coordination paths — different semantics, one room.

All ride the same WebSocket signaling connection with typed protocol envelopes. Your product UX stays coherent; your backend stays auditable.

Need a transient click or stroke?

SharedSignal — broadcast or unicast, optional TTL, rate-limited.

Need late-joiners to see current user state?

Awareness — patch-merge blobs held in room memory until leave.

Need searchable conversation history?

Chat — server relay + optional Postgres/Mongo/Redis with AES-256-GCM at rest.

Need large or binary payloads?

Data channel — WebRTC datachannel with schema helpers in the browser SDK.

SharedSignals

Typed ephemeral events at cursor speed.

Define a SignalMap for compile-time payload typing. Broadcast to the room or unicast to one participant. Server validates, rate-limits, and fans out — but never stores.

Permissions
room:send-signal (broadcast) · room:send-private-message (unicast)
Limits
120 signals/participant/minute · 8 KB payload max
Hook
onSharedSignal — allow-list, audit, or drop before relay
  • Namespaced kinds cursor:move, myapp.annotation.add avoid collisions.
  • TTL drops stale frames Server ignores signals that arrive too late for smooth UI.
  • Echo to sender Clients filter own senderId when rendering remotes.
  • Node workers too Service bots can send and receive signals in the same room.

Awareness

Per-participant state that survives disconnect — and syncs on join.

Each participant owns one mergeable state blob: status, active tool, viewport, display overrides. New joiners receive a full snapshot; updates broadcast as patches. When someone leaves, peers get a tombstone — no ghost cursors.

Snapshot on join Late-joiner sync

Unlike signals, awareness state is held server-side for the room lifetime.

Patch merge Partial updates

setLocalState({ status: "away" }) without resending the whole blob.

Typed generics RoomAwareness<T>

Compile-time safety for tool, status, and custom product fields.

4 KB cap Security

Prototype-pollution guard rejects dangerous keys server-side.

Decision guide

Signals vs awareness — quick reference.

Use SharedSignal when…

  • Event is transient / fire-and-forget
  • Per-event: click, stroke, reaction, tick
  • High frequency (cursor at 30+ Hz)
  • Unicast to one participant

Use Awareness when…

  • Late-joiners must see current value
  • Per-participant state: status, tool, selection
  • State should survive brief disconnect
  • Roster or presence indicators
Note
Note Slide context
Full guide in the SharedSignals & Awareness section of the monorepo docs. SharedSignals and Awareness are intentionally separate from the chat pipeline — do not route cursor frames through chat messages.

Chat & messaging

Room chat with receipts, replay, and durable storage options.

Chat uses chat.send / chat.message envelopes with optional delivery and read receipts. In-memory history (1,000 messages) supports reconnect replay; plug in MongoDB, Redis, or Postgres for horizontal scale and encryption at rest.

  • Worker output AI transcription and assistant events arrive as chat with workerOutput metadata.
  • Replay on reconnect Cursor-based messageReplayed after WebSocket resume.
  • Dedup Retried sends keyed by clientMessageId.
Browser / Node

room.sendMessage()

Receipts

delivered · read · reconcile on reconnect

Message bus Signaling server Validate · fan-out · optional storage adapter
Storage

Memory · Mongo · Redis · Postgres

Encryption

AES-256-GCM at rest when key set

See it live

Runnable demos — two tabs, immediate proof.

The shared-signals example combines cursor tracking, emoji reactions, and awareness-driven roster state. Chat-room demo focuses on messaging and presence. Kitchen-sink exercises all primitives together with voice and video.

shared-signals port 5183

Collaborative pointer canvas — cursor:move at ~30 Hz with TTL, reactions, tool/status awareness.

chat-room port 5174

Multi-user chat, presence, and receipts — minimal messaging-focused demo.

kitchen-sink port 5175

Full product demo — chat, data channel file send, signals panel, worker output stream.

Documentation docs/

shared-signals-awareness.md · chat-storage.md · signaling.md message types.

Coordinate your product UI — then add voice and AI in the same room.

Signals and chat are half the story. Pair this deck with voice/video and voice AI for the full TomatoRTC narrative.

  1. Run shared-signals

    Two tabs on localhost — cursors, reactions, awareness roster

  2. Read the guide

    SharedSignals & Awareness integration guide

  3. Related decks

    Voice & video · Voice AI · Overview