Skip to main content
CollaborationManager lets your app create and join real-time spaces. The platform handles all networking, state synchronization, persistence, and conflict resolution — your app just sends and receives state. This is what powers the built-in Whiteboard and Document Editor, and it’s available to every custom app.

How collaboration works

  1. One user hosts a space, which generates a space code
  2. Other users join using that code
  3. Participants exchange state updates through the platform
  4. The platform broadcasts changes, persists state, and resolves conflicts via
Your app doesn’t need to think about , signaling servers, or data channels. The platform abstracts all of that.

Accessing CollaborationManager

const getCollab = () =>
  window.CollaborationManager ?? window.parent?.CollaborationManager ?? null;
CollaborationManager is only available when your app runs inside the webAI shell. In local development, it will be null. Always guard your calls.

Hosting a space

Create a new collaboration space that other users can join:
async function hostRoom(roomName, password = null) {
  const collab = getCollab();
  if (!collab) {
    console.warn('CollaborationManager not available.');
    return null;
  }
  return collab.hostRoom({ roomName, password });
}

Parameters

ParameterTypeDescription
roomNamestringA display name for the space
passwordstring | nullOptional password to restrict access

Return value

Returns the space state object, which includes the generated space code that others use to join.

Joining a space

Join an existing space using its code:
async function joinRoom(roomCode, password = null) {
  const collab = getCollab();
  if (!collab) return null;
  return collab.joinRoom(roomCode, password);
}

Parameters

ParameterTypeDescription
roomCodestringThe code of the space to join
passwordstring | nullPassword, if the space requires one

Leaving a space

function disconnect() {
  const collab = getCollab();
  collab?.disconnect();
}

Getting space info

Check the current space settings — the room name, whether it’s public, and other configuration:
function getRoomSettings() {
  const collab = getCollab();
  return collab?.getRoomSettings?.() ?? null;
}

Getting connected users

See who’s currently in the space:
function getUsers() {
  const collab = getCollab();
  return collab?.getUsers?.() ?? new Map();
}

Chat and messaging

Send messages within the space or directly to a specific peer:
// Send a message to the space chat
function sendChatMessage(text) {
  const collab = getCollab();
  collab?.sendChatMessage(text);
}

// Send a direct message to a specific user
async function sendDirectMessage(toOdid, text) {
  const collab = getCollab();
  await collab?.sendDirectMessage(toOdid, text);
}

// React to a message
function sendReaction(messageId, emoji) {
  const collab = getCollab();
  collab?.sendReaction(messageId, emoji);
}

Edit locking

Prevent conflicts when multiple users edit the same field:
// Request a lock on a field before editing
async function requestLock(fieldKey) {
  const collab = getCollab();
  await collab?.requestLock(fieldKey);
}

// Release the lock when done
function releaseLock(fieldKey) {
  const collab = getCollab();
  collab?.releaseLock(fieldKey);
}
Locks expire automatically after 30 seconds if not released. See Edit locking for more detail.

File sharing

Share files with everyone in the space or send privately to a specific user:
// Share a file with the entire space
async function shareFile(file) {
  const collab = getCollab();
  await collab?.shareFile(file);
}

// Send a file privately to one user
async function sendPrivateFile(toOdid, file, messageId) {
  const collab = getCollab();
  await collab?.sendPrivateFile(toOdid, file, messageId);
}

Voice

Join or leave the space’s voice channel:
async function joinVoice() {
  const collab = getCollab();
  await collab?.joinVoice();
}

function leaveVoice() {
  const collab = getCollab();
  collab?.leaveVoice();
}

Event listening

Subscribe to collaboration events to update your UI in real time:
const collab = getCollab();
const unsub = collab?.addListener((event) => {
  // Handle state changes, user joins/leaves, chat messages, etc.
});

// Later, to unsubscribe:
unsub?.();

Building a collaboration helper module

For cleaner code, wrap all collaboration logic in a dedicated module:
// src/collab.js
export const getCollab = () =>
  window.CollaborationManager ?? window.parent?.CollaborationManager ?? null;

export const getUserIdentity = () =>
  window.UserIdentityManager ?? window.parent?.UserIdentityManager ?? null;

export async function hostRoom(roomName, password = null) {
  const collab = getCollab();
  if (!collab) {
    console.warn('[collab] CollaborationManager not available.');
    return null;
  }
  return collab.hostRoom({ roomName, password });
}

export async function joinRoom(roomCode, password = null) {
  const collab = getCollab();
  if (!collab) return null;
  return collab.joinRoom(roomCode, password);
}

export function disconnect() {
  const collab = getCollab();
  collab?.disconnect();
}

export function getRoomSettings() {
  const collab = getCollab();
  return collab?.getRoomSettings?.() ?? null;
}

export function getUsers() {
  const collab = getCollab();
  return collab?.getUsers?.() ?? new Map();
}

export async function getIdentity() {
  const im = getUserIdentity();
  if (!im) return { odid: 'local', displayName: 'You' };
  return im.getOrCreateIdentity();
}

Framework integration

import { useState, useEffect } from 'react';
import { hostRoom, joinRoom, disconnect, getRoomSettings, getIdentity } from './collab';

function CollabPanel() {
  const [roomSettings, setRoomSettings] = useState(null);
  const [identity, setIdentity] = useState(null);

  useEffect(() => {
    getIdentity().then(setIdentity);
  }, []);

  async function handleHostRoom() {
    await hostRoom('My Space');
    setRoomSettings(getRoomSettings());
  }

  async function handleJoinRoom(code) {
    await joinRoom(code);
    setRoomSettings(getRoomSettings());
  }

  function handleDisconnect() {
    disconnect();
    setRoomSettings(null);
  }

  return (
    <div>
      <p>Signed in as: {identity?.displayName ?? 'Loading...'}</p>
      {roomSettings ? (
        <div>
          <p>In space: {roomSettings.roomName}</p>
          <button onClick={handleDisconnect}>Leave</button>
        </div>
      ) : (
        <div>
          <button onClick={handleHostRoom}>Host Space</button>
          <button onClick={() => handleJoinRoom(prompt('Space code:'))}>
            Join Space
          </button>
        </div>
      )}
    </div>
  );
}

Best practices

The API is only injected inside the webAI shell. Wrap every call in a null check so your app doesn’t crash during local development or in environments where collaboration isn’t available.
Always check space state before calling space-specific methods. The user might not be in a space yet, or they may have disconnected.
If a space is password-protected, don’t store or display the password in your UI or in localStorage. Let the user enter it each time.
Peers can drop out at any time. Design your app’s state model to handle partial participation — not every user may be present for every update.

Next steps