Skip to content

Raw Channels & Connection Info

Beyond the shared data structures, the P2P module gives you direct access to peer-to-peer messaging and connection state.

Raw Data Channels

Send any data directly to peers without CRDT overhead. Useful for custom sync protocols, ephemeral events, or binary data.

Broadcast to All Peers

typescript
const p2p = await client.p2p;

// Send to every connected peer
p2p.send({ type: 'cursor-highlight', x: 100, y: 200, color: '#ff0' });
p2p.send('simple string message');

Send to Specific Peer

typescript
p2p.sendTo('user-123', { type: 'private-ping', timestamp: Date.now() });

Receive Messages

typescript
p2p.on('message', (msg) => {
  // msg.data — the parsed message (JSON parsed automatically, or raw string)
  // msg.fromUserId — who sent it
  console.log(`${msg.fromUserId}: ${msg.data}`);
});
MethodDescription
send(data)Broadcast to all connected peers
sendTo(userId, data)Send to a specific peer
on('message', handler)Listen for incoming messages

Serialization

Objects are automatically JSON-serialized when sending and JSON-parsed when receiving. Strings are sent as-is.

Use Cases

  • Custom cursor protocols (higher frequency than Pulse's built-in cursors)
  • Ephemeral reactions / effects
  • Lightweight game state
  • Direct user-to-user communication

Connection Info

Peer List

typescript
const peers = p2p.peers;  // Map<string, PeerState>

for (const [userId, state] of peers) {
  console.log(userId, state.state, state.dataChannelOpen);
}

Each PeerState has:

FieldTypeDescription
userIdstringThe peer's user ID
state'connecting' | 'connected' | 'disconnected'WebRTC connection state
dataChannelOpenbooleanWhether the data channel is ready for messages

Current Transport

typescript
console.log(p2p.transport);  // 'p2p' or 'websocket'
  • 'p2p' — Data flows directly between browsers via WebRTC
  • 'websocket' — Fallback mode: data relayed through the Pulse server

Events

typescript
// Peer connected via WebRTC
p2p.on('peer:connected', (userId) => {
  console.log(`${userId} connected via P2P`);
});

// Peer disconnected
p2p.on('peer:disconnected', (userId) => {
  console.log(`${userId} disconnected`);
});

// Transport changed (e.g., fell back from P2P to WebSocket)
p2p.on('transport:change', (transport) => {
  console.log(`Now using: ${transport}`);
});
EventPayloadWhen
peer:connecteduserId: stringWebRTC connection established with a peer
peer:disconnecteduserId: stringPeer's WebRTC connection closed
transport:change'p2p' | 'websocket'Sync transport switched

Mesh Limit & Fallback

P2P uses a full-mesh topology — every peer connects to every other peer. This works well for small groups (2-6 users) but doesn't scale to large rooms.

When the peer count exceeds the mesh limit (configurable per environment, default 6):

  1. New peers skip WebRTC and use the WebSocket fallback
  2. CRDT updates flow through the Pulse server instead of peer-to-peer
  3. Shared data structures work identically — your code doesn't change
  4. The transport:change event fires with 'websocket'
typescript
p2p.on('transport:change', (transport) => {
  if (transport === 'websocket') {
    console.log('Room is large — using server relay');
  }
});

ICE Servers (STUN/TURN)

WebRTC needs STUN/TURN servers to establish connections through NATs and firewalls.

Default

By default, Pulse uses Google's public STUN server (stun:stun.l.google.com:19302). This works for most cases but doesn't include a TURN relay.

Custom STUN/TURN

You can configure a TURN server in the Admin Panel → Environment Settings → TURN Server. When configured, the SDK automatically fetches time-limited credentials.

Or pass ICE servers manually:

typescript
const client = new PulseClient({
  apiKey: 'pk_...',
  token: userToken,
  room: 'my-room',
  endpoint: 'wss://pulse.example.com',
  p2p: true,
  iceServers: [
    { urls: 'stun:stun.example.com:3478' },
    { urls: 'turn:turn.example.com:3478', username: '...', credential: '...' },
  ],
});

TURN Credentials API

If your environment has a TURN server configured, you can also fetch credentials via the REST API:

bash
curl https://pulse.example.com/api/v1/turn-credentials \
  -H "Authorization: Bearer sk_..."

Response:

json
{
  "username": "1712000000:env-id:turn",
  "credential": "base64-hmac",
  "ttl": 86400,
  "iceServers": [
    { "urls": "stun:turn.example.com:3478" },
    { "urls": "turn:turn.example.com:3478", "username": "...", "credential": "..." }
  ]
}

Cleanup

The P2P module is cleaned up automatically when you call client.destroy(). If you need manual cleanup:

typescript
p2p.destroy();

This:

  • Closes all WebRTC peer connections
  • Persists final CRDT state to the server
  • Cleans up Yjs document and observers
  • Removes all event listeners

Pulse Collaboration SDK