Without Weei's UI

Call setVisibility(false) to remove all of Weei's UI from the page: no control panel, no notifications, no participant video tiles. Your page uses the APIs below instead. Widget windows open only when your page opens them with widgetsApi. Call it right after loading, so the UI never appears.

JavaScript
const loaderUrl = 'https://weei-core.web.app/loader.min.js';
window.weeiReady ??= (async () => {
  const { loadWeei } = await import(loaderUrl + '?t=' + Date.now());
  const weei = await loadWeei();
  weei.setVisibility(false);
  return weei;
})();
Shown (default)setVisibility(false)
Control panelShown as your site settings allowNot built
NotificationsShown on the page and delivered to onNotice()Only delivered to onNotice()
Participant video tiles, screen previewShownNot built; use media.getState().remote
peers, rooms, mediaAvailableAvailable

The APIs below also work while the UI is shown, alongside it. Every API has the same shape as the widget APIs: getState() returns a snapshot, subscribe(listener) calls listener at once with the current state and again on every change, and returns a function that unsubscribes. Snapshots are replaced, never changed in place.

peers

TypeScript
interface WeeiPeersApi {
  getState(): WeeiPeersState;
  subscribe(listener: (state: WeeiPeersState) => void): () => void;
  onEvent(listener: (event: WeeiPeerEvent) => void): () => void;
  connect(peerId: string, options?: { timeoutMs?: number }): Promise<void>;
  disconnect(peerId: string): void;
}

interface WeeiPeersState {
  me: { peerId: string; displayName: string }; // peerId is '' until the session connects
  online: WeeiPeer[];   // other participants online on this site and room
  connected: string[];  // peer IDs with an open connection
}

interface WeeiPeer {
  peerId: string;
  displayName: string;  // the name from setUser(), or the peer ID
  connected: boolean;
  connecting: boolean;  // connect() was called and has not settled
}

type WeeiPeerEvent =
  | { type: 'connected'; peerId: string }
  | { type: 'disconnected'; peerId: string }
  | { type: 'rejected'; peerId: string; reason: string; message: string }
  | { type: 'cancelled'; peerId: string; reason: string; message: string };

connect(peerId) asks the server to connect the two participants and resolves when the connection is open. It resolves at once if the connection is already open; calling it again while connecting returns the same outcome. It rejects with a WeeiConnectionError whose reason is one of:

reasonMeaning
peerUnavailableThe peer is no longer online.
differentRoomThe peer is in another room. Join the same room first.
connectionInProgressThe server is already connecting these two participants.
connectionAlreadyEstablishedThey are already connected.
peerDisconnected, roomChanged, requestExpired, userDisconnectedThe request or connection was called off.
timeoutNot connected within timeoutMs (default 30 seconds).
disconnecteddisconnect() was called while connecting.
JavaScript
const weei = await window.weeiReady;
weei.peers.subscribe(({ me, online }) => {
  renderParticipants(online.map(peer => ({
    name: peer.displayName,
    status: peer.connected ? 'connected' : peer.connecting ? 'connecting…' : 'online',
    onClick: () => weei.peers.connect(peer.peerId).catch(error => showError(error.message)),
  })));
});
weei.peers.onEvent(event => {
  if (event.type === 'disconnected') console.log('Connection closed:', event.peerId);
});

rooms

TypeScript
interface WeeiRoomsApi {
  getState(): WeeiRoomsState;
  subscribe(listener: (state: WeeiRoomsState) => void): () => void;
  join(roomId: string | null): void;
}

interface WeeiRoomsState {
  current: string | null;  // null is the site's shared lobby
  meetings: WeeiMeeting[]; // scheduled meetings of this site
  members: string[];       // peer IDs in the current room, this participant included
}

interface WeeiMeeting {
  uuid: string;            // join a meeting with rooms.join(meeting.uuid)
  title: string;
  startsAt: string;
  expectedEndsAt: string;
}

join() moves this participant to a meeting's room, or back to the lobby with null. Open connections close when the room changes; participants can connect only within the same room.

JavaScript
const { meetings } = weei.rooms.getState();
weei.rooms.join(meetings[0].uuid);
weei.rooms.subscribe(({ current, members }) => console.log(current, members.length));

media

TypeScript
interface WeeiMediaApi {
  getState(): WeeiMediaState;
  subscribe(listener: (state: WeeiMediaState) => void): () => void;
  setCamera(enabled: boolean): Promise<void>;
  setMicrophone(enabled: boolean): Promise<void>;
  setScreenSharing(enabled: boolean): Promise<void>;
  switchCamera(): Promise<void>;     // the next camera, e.g. front and back on a phone
}

interface WeeiMediaState {
  camera: boolean;
  microphone: boolean;
  screen: boolean;
  local: { camera: MediaStream | null; microphone: MediaStream | null; screen: MediaStream | null };
  remote: Record<string, MediaStream>; // each connected participant's stream, by peer ID
}

The setters change only what differs from the current state: setCamera(true) with the camera already on does nothing. The browser may ask for permission; a refusal rejects the promise. A participant's stream carries their camera, microphone and shared-screen tracks; attach it to your own <video> element.

JavaScript
await weei.media.setCamera(true);
await weei.media.setMicrophone(true);

const videos = new Map();
weei.media.subscribe(({ local, remote }) => {
  document.querySelector('#me').srcObject = local.camera;
  for (const [peerId, stream] of Object.entries(remote)) {
    let video = videos.get(peerId);
    if (!video) {
      video = Object.assign(document.createElement('video'), { autoplay: true, playsInline: true });
      document.querySelector('#participants').append(video);
      videos.set(peerId, video);
    }
    if (video.srcObject !== stream) video.srcObject = stream;
  }
  for (const [peerId, video] of videos)
    if (!remote[peerId]) { video.remove(); videos.delete(peerId); }
});

onNotice(listener)

TypeScript
interface WeeiNotice {
  message: string;
  type: 'info' | 'success' | 'error';
  widgetId?: string;            // the widget the notice is about
  open?: () => Promise<void>;   // opens what the notice is about, as clicking it would
}

Delivers the notifications Weei shows, such as connection changes or a new chat message. While the UI is hidden with setVisibility(false), this is the only place they arrive. Returns a function that unsubscribes.

JavaScript
weei.onNotice(notice => {
  showToast(notice.message, notice.type, notice.open);
});

Example: a minimal call page

HTML
<button id="camera">Camera</button>
<ul id="people"></ul>
<video id="me" autoplay muted playsinline></video>
<div id="participants"></div>
<script type="module">
  const { loadWeei } = await import('https://weei-core.web.app/loader.min.js?t=' + Date.now());
  const weei = await loadWeei();
  weei.setVisibility(false);
  await weei.setUser({ id: 'user-42', displayName: 'Ada' });

  document.querySelector('#camera').onclick = () =>
    weei.media.setCamera(!weei.media.getState().camera);

  weei.peers.subscribe(({ online }) => {
    document.querySelector('#people').replaceChildren(...online.map(peer => {
      const item = document.createElement('li');
      item.textContent = peer.displayName + (peer.connected ? ' — connected' : '');
      item.onclick = () => weei.peers.connect(peer.peerId).catch(error => alert(error.message));
      return item;
    }));
  });

  weei.media.subscribe(({ local, remote }) => {
    document.querySelector('#me').srcObject = local.camera;
    document.querySelector('#participants').replaceChildren(...Object.values(remote).map(stream =>
      Object.assign(document.createElement('video'), { srcObject: stream, autoplay: true, playsInline: true })));
  });
</script>

Replacing every <video> on each change, as this short example does, restarts playback; keep elements by peer ID as in the media example for a real page.