Skip to content

Latest commit

 

History

4 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Pexip External Chat Plugin

A Pexip Web App 3 plugin that adds a Chat toolbar button and bridges the meeting to an external chat experience hosted by the page that embeds Web App 3 (the top window).

The plugin and the top window communicate over window.postMessage. The plugin also exposes the Web App 3 conference.dialOut capability so the top window can dial participants into the meeting.

┌──────────────────────────────┐
│ Top window (embedding page)  │
│                              │
│   ┌───────────────────────┐  │   postMessage (both directions)
│   │ Web App 3 (iframe)    │  │
│   │   ┌────────────────┐  │◄─┼────────────────────────────────┐
│   │   │ external-chat  │──┼──┼────────────────────────────────┘
│   │   │   plugin       │  │  │
│   │   └────────────────┘  │  │
│   └───────────────────────┘  │
└──────────────────────────────┘

Repository layout

.
├── Makefile                       # build + package pipeline (run from repo root)
├── external-chat/                 # the plugin source (Vite + TypeScript)
│   ├── src/index.ts               # plugin entry point
│   └── package.json               # version is the source of truth for releases
└── webapp3/
    └── branding/
        ├── manifest.json          # Web App 3 branding manifest (registers the plugin)
        └── plugins/external-chat/ # built plugin is copied here by `make deploy`

The plugin id in external-chat/src/index.ts (registerPlugin({ id: 'external-chat' })) must match the plugin id in webapp3/branding/manifest.json (plugins[].id). A mismatch causes Web App 3 to reject the plugin at load time.


postMessage API

All messages — in both directions — share a single envelope: a flat object with an action string plus any payload fields alongside it.

{ action: 'pexip:plugin:external-chat/<name>', ...payloadFields }

Every action is namespaced with the pexip:plugin:external-chat/ prefix. Messages without that prefix are ignored (and logged as a warning).

Security

The plugin only reacts to messages whose event.source is the top window. Messages originating from the plugin's own iframe or sibling frames are dropped. If your deployment has a fixed host origin, you should additionally gate on event.origin in src/index.ts for defense in depth.


Messages sent BY the plugin (plugin → top window)

Listen for these in the top window:

window.addEventListener('message', (event) => {
  if (event.source !== document.querySelector('iframe#webapp3')?.contentWindow) return;
  const { action, ...data } = event.data ?? {};
  switch (action) {
    case 'pexip:plugin:external-chat/ready':            /* plugin is loaded */ break;
    case 'pexip:plugin:external-chat/connected':        /* user joined the call */ break;
    case 'pexip:plugin:external-chat/disconnected':     /* data.userInitiated, data.error, data.errorCode */ break;
    case 'pexip:plugin:external-chat/toggle-chat':      /* data.active */      break;
    case 'pexip:plugin:external-chat/dial-out-success': /* data.uuid, data.displayName */ break;
    case 'pexip:plugin:external-chat/dial-out-error':   /* data.message */     break;
  }
});
Action Payload When
pexip:plugin:external-chat/ready (none) Plugin finished loading and registering (before the user joins).
pexip:plugin:external-chat/connected (none) User joined the call (passed preflight); the in-meeting toolbar and Chat button are now visible/accessible.
pexip:plugin:external-chat/disconnected { userInitiated: boolean, error?: string, errorCode?: string } User left or lost the call; the toolbar is no longer available. userInitiated is true when the user clicked Leave, false for an involuntary drop (in which case error/errorCode are set).
pexip:plugin:external-chat/toggle-chat { active: boolean } User clicked the Chat toolbar button. active is the requested state (the opposite of the current one).
pexip:plugin:external-chat/dial-out-success { uuid: string, displayName?: string } A dial-out request succeeded; the dialed participant joined.
pexip:plugin:external-chat/dial-out-error { message: string } A dial-out request failed.

Note: Clicking the toolbar button only notifies the top window via toggle-chat. The button's active/tooltip state does not change on its own — the top window is the source of truth and must echo back toggle-chat-button-state (below) to update the button.


Messages handled BY the plugin (top window → plugin)

Send these from the top window to the Web App 3 iframe:

const iframe = document.querySelector('iframe#webapp3');
iframe.contentWindow.postMessage({
  action: 'pexip:plugin:external-chat/toggle-chat-badge',
  visible: true,
}, '*'); // use the Web App 3 origin instead of '*' in production
Action Payload Effect
pexip:plugin:external-chat/toggle-chat-button-state { active: boolean } Sets the Chat button's active state and tooltip (Close Chat / Open Chat).
pexip:plugin:external-chat/toggle-chat-badge { visible: boolean } Shows or hides the unread badge on the Chat button.
pexip:plugin:external-chat/dial-out dial parameters (see below) Dials a destination into the conference via conference.dialOut. Replies with dial-out-success / dial-out-error.

dial-out parameters

These map directly to the Pexip Infinity dial request body.

Field Type Required Description
role 'HOST' | 'GUEST' ✅ Privilege level of the dialed participant.
destination string ✅ The target address to call.
protocol 'sip' | 'h323' | 'rtmp' | 'mssip' | 'auto' ✅ Protocol used to place the call.
call_type 'video' | 'video-only' | 'audio' — Limits the media content of the call.
presentation_url string — For RTMP calls, sends presentation to a separate destination.
streaming 'yes' | 'no' — Marks the participant as a streaming/recording device.
dtmf_sequence string — DTMF tones to send once the call connects.
source string — Source URI (must be valid for the conference).
source_display_name string — Calling display name.
remote_display_name string — Friendly name shown in participant lists / overlays.
text string — Overlay text used instead of remote_display_name.
keep_conference_alive 'keep_conference_alive' | 'keep_conference_alive_if_multiple' | 'keep_conference_alive_never' — Whether the conference continues after others leave.

Example:

iframe.contentWindow.postMessage({
  action: 'pexip:plugin:external-chat/dial-out',
  role: 'GUEST',
  destination: 'alice@example.com',
  protocol: 'sip',
  call_type: 'video',
  remote_display_name: 'Alice',
}, '*');

conference.dialOut resolves only once the dialed participant actually joins. Hard failures (e.g. an invalid URI) reject and produce dial-out-error, but a destination that simply never answers will neither resolve nor error.


Setup

Prerequisites: Node.js 18+ (developed on Node 22) and npm.

cd external-chat
npm install

Develop

npm start

Vite serves the plugin from https://localhost:5173 (self-signed cert via vite-plugin-mkcert — accept it in the browser once). You access it through your Web App 3 URL configured to load the plugin from this dev server. See the Pexip setup guide.

Build (standalone)

npm run build   # outputs to external-chat/dist/

The current version is read from external-chat/package.json and is logged by the plugin at runtime (plugin: external-chat loaded v<version>).


Building & packaging with the Makefile

Run all make commands from the repository root.

make            # full release: bump the minor version, build, then package
make package    # build & package using the CURRENT version (no bump)

Targets

Target What it does Bumps version?
make release Bumps the version, then runs package. ✅
make all Alias for make release (the default target). ✅
make bump Bumps the minor version in external-chat/package.json (npm version minor --no-git-tag-version — no git commit or tag). ✅
make build Runs npm run build. —
make deploy Runs build, then copies external-chat/dist/ into webapp3/branding/plugins/external-chat/. —
make package Runs deploy, then zips the webapp3/ folder into external-chat-v<version>.zip at the repo root, using the current package.json version. —
make clean Removes external-chat/dist/ and the deployed plugin folder. —

Dependency chains: release → bump → package → deploy → build. Only release, all, and bump change the version — build/deploy/package use the current one, so CI and one-off packaging never bump (CI runs make package).

Output

make produces external-chat-v<version>.zip in the repo root, where <version> is the value in package.json (e.g. external-chat-v1.4.0.zip) — freshly bumped by make/make release, or unchanged by make package. The zip contains the full webapp3/ branding bundle, ready to upload.

Continuous integration

.github/workflows/ci.yml runs on pushes to main, on pull requests, and on manual dispatch. It installs dependencies (npm ci), then runs lint, typecheck (tsc --noEmit), and make package (build + zip, no version bump). The resulting external-chat-v<version>.zip is uploaded as a downloadable workflow artifact named external-chat-v<version>.

Releasing

.github/workflows/release.yml is a manually triggered workflow (Actions → Release → Run workflow). It reads the current version from external-chat/package.json, builds the zip (make package, no bump), then creates a v<version> git tag and a GitHub release with external-chat-v<version>.zip attached as an asset. Release notes are auto-generated from the changes since the previous release.

Typical flow:

make bump                 # 1. bump the minor version in package.json
git commit -am "release"  # 2. commit the bump (and push)
# 3. run the "Release" workflow from the Actions tab

If a release for the current version already exists, the workflow fails with a reminder to bump first (it never overwrites an existing tag/release).

About

Pexip External Chat integration for embedded experience

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages