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-embed

Use

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

EventWhenpayload
readythe /join surface loaded (pre-join screen){}
joinedthe participant connected to the room{}
leftthe connection ended, incl. a normal leave{ reason }
erroran abnormal disconnect (also preceded by left){ reason }
participant.joined / participant.leftroster changes{ identity }
recording.started / recording.stoppedrecording 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-ancestors posture.

On this page