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.
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 panel | Shown as your site settings allow | Not built |
| Notifications | Shown on the page and delivered to onNotice() | Only delivered to onNotice() |
| Participant video tiles, screen preview | Shown | Not built; use media.getState().remote |
peers, rooms, media | Available | Available |
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
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:
| reason | Meaning |
|---|---|
peerUnavailable | The peer is no longer online. |
differentRoom | The peer is in another room. Join the same room first. |
connectionInProgress | The server is already connecting these two participants. |
connectionAlreadyEstablished | They are already connected. |
peerDisconnected, roomChanged, requestExpired, userDisconnected | The request or connection was called off. |
timeout | Not connected within timeoutMs (default 30 seconds). |
disconnected | disconnect() was called while connecting. |
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
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.
const { meetings } = weei.rooms.getState();
weei.rooms.join(meetings[0].uuid);
weei.rooms.subscribe(({ current, members }) => console.log(current, members.length));media
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.
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)
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.
weei.onNotice(notice => {
showToast(notice.message, notice.type, notice.open);
});Example: a minimal call page
<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.