Embed SDK (@etiteltd/meet-embed)
npm wrapper over the embedded meeting UI — typed events, commands, correct iframe setup.
@etiteltd/meet-embed is a thin, framework-agnostic wrapper (zero dependencies,
~3 KB) over the iframe embed. It bundles no call
UI — the meeting interface is always the hosted /join surface served by
the Meet deployment, so every fix and feature reaches your users without an
upgrade on your side. The package just gives you a correctly configured
iframe, typed lifecycle events, and a few commands.
Prefer it over hand-rolling the iframe: it sets the allow permission list,
appends parentOrigin (which activates the event bridge), validates message
origins on both directions, and never uses targetOrigin: '*'.
Install
npm install @etiteltd/meet-embedUse
The API half stays server-side: your backend creates the room and mints a
per-participant join token with your tenant API key, then hands the response's
joinUrl to your frontend. The key never reaches the browser.
import { embed } from '@etiteltd/meet-embed';
const joinUrl = await fetchJoinUrlFromYourBackend(meetingId);
const call = embed({
container: document.getElementById('call')!,
joinUrl, // for an E2EE room: joinUrl + '#e2ee_key=' + key
});
call.on('ready', () => console.log('pre-join screen loaded'));
call.on('joined', () => console.log('connected'));
call.on('participant.joined', ({ identity }) => console.log('+', identity));
call.on('left', ({ reason }) => {
console.log('call ended', reason);
call.destroy();
});
// Commands
call.toggleAudio();
call.toggleVideo();
call.leave();embed() returns a MeetEmbed instance; call.iframe is the underlying
element if you need to style or measure it. on() returns an unsubscribe
function, and on('*', handler) receives every event.
Events
| Event | When | payload |
|---|---|---|
ready | the /join surface loaded (pre-join screen) | {} |
joined | the participant connected to the room | {} |
left | the connection ended, incl. a normal leave | { reason } |
error | an abnormal disconnect (also preceded by left) | { reason } |
participant.joined / participant.left | roster changes | { identity } |
recording.started / recording.stopped | recording state | {} |
The protocol is versioned and additive — ignore events and payload fields you don't recognize.
Waiting rooms
If the join response parked the participant (waitingForApproval: true), its
joinUrl renders the waiting screen. Poll
GET /rooms/:roomId/participants/:participantId/admission from your backend
(see Waiting rooms) and swap in the admitted
joinUrl — either call.iframe.src = newJoinUrl or destroy and re-embed.
React example
The SDK is framework-agnostic; in React, wrap it in an effect:
function MeetCall({ joinUrl, onEnded }: { joinUrl: string; onEnded: () => void }) {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!ref.current) return;
const call = embed({ container: ref.current, joinUrl });
const off = call.on('left', onEnded);
return () => {
off();
call.destroy();
};
}, [joinUrl, onEnded]);
return <div ref={ref} style={{ height: '100dvh' }} />;
}Security notes
- Serve your page over HTTPS — camera/microphone permission cannot be delegated to an iframe otherwise.
- Events are accepted only from the joinUrl's origin; commands are posted only to it; the embedded page emits only to your origin.
- Everything else from the embedding guide applies —
permission delegation table, fragment preservation for E2EE, and
frame-ancestorsposture.