Skip to content

Repository files navigation

Jitsi Meet Electron SDK

SDK for integrating Jitsi Meet into Electron applications.

The SDK is built for contextIsolation: true (and, where possible, sandbox: true) on the window that hosts Jitsi Meet, following Electron's security guidelines. Native code that used to run in the renderer (remote control via robotjs) now runs only in the main process, and every renderer ↔ main message goes through a single, validated contextBridge surface.

Upgrading from v9? The single require('@jitsi/electron-sdk') entry has been removed and replaced by three context-specific entry points. See Migrating from v9.

Installation

Install from npm:

npm install @jitsi/electron-sdk

Note: This package contains native code on Windows for the remote control module. Binary prebuilds are packaged with prebuildify as part of the npm package. @jitsi/robotjs is a dependency but is only ever loaded in the main process.

Architecture

The SDK ships three entry points, each named after the Electron context its code runs in. There is no default (.) entry β€” importing @jitsi/electron-sdk directly fails with a module-resolution error by design.

Entry point Runs in Exposes
@jitsi/electron-sdk/main Electron main process setupRemoteControlMain, setupScreenSharingMain, setupPowerMonitorMain, cleanupPowerMonitorMain, setupPictureInPictureMain, initPopupsConfigurationMain, getPopupTarget, popupsConfigRegistry
@jitsi/electron-sdk/preload app preload script install() β€” exposes the SDK bridge on the main world via contextBridge
@jitsi/electron-sdk/renderer the page ("main world") setupRemoteControlRender, setupScreenSharingRender, setupPowerMonitorRender, setupPictureInPictureRender, initPopupsConfigurationRender
╔═ renderer process ═══════════════════════════════════════════════╗
β•‘  page / "main world"  β†’  @jitsi/electron-sdk/renderer             β•‘
β•‘    owns api.* events + postis; no electron/node/native requires   β•‘
β•‘                     β”‚  window.jitsiElectronSDK (validated bridge) β•‘
β•‘  preload / isolated  β†’  @jitsi/electron-sdk/preload               β•‘
β•‘    thin ipcRenderer wrappers, per-feature, payload-validated      β•‘
β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β”‚β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
                      β”‚  IPC β€” namespaced, sender-validated channels
β”Œβ• main process ══════┴═════════════════════════════════════════════┐
β”‚  @jitsi/electron-sdk/main  β†’  setup*Main (+ robotjs execution)     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

The bridge (window.jitsiElectronSDK)

The preload entry exposes one namespaced object to the page. Renderer code never touches ipcRenderer directly β€” it talks to the main process only through this surface. Only cloneable data crosses the bridge; callbacks are supported as subscriptions that return an unsubscribe function. Every bridge-backed setup*Render reads its fragment off window.jitsiElectronSDK, so the preload must be installed β€” and the window created with context isolation β€” before they are called. (initPopupsConfigurationRender is the one exception: it is a no-op and does not use the bridge.)

Setup

1. Window configuration

Create the window that hosts Jitsi Meet with context isolation on and a preload that installs the bridge:

const jitsiMeetWindow = new BrowserWindow({
    webPreferences: {
        contextIsolation: true,
        sandbox: true, // robotjs no longer runs in the renderer, so the page can be sandboxed
        preload: '/absolute/path/to/your/bundled/preload.js'
    }
});

2. Preload script

A sandboxed preload cannot require from node_modules, so the app's preload must be bundled (esbuild, webpack, etc.). Call install() from it to expose the bridge:

// app preload (bundled)
import { install } from '@jitsi/electron-sdk/preload';

install();

install() puts the bridge on the main world under an SDK-internal key that the /renderer helpers read for you β€” your code never names it. Call it once, from a preload running with contextIsolation enabled.

3. Main process

Set up the main-process components before the renderer components, and clean handlers up on window close to prevent leaks:

const { setupScreenSharingMain } = require('@jitsi/electron-sdk/main');

setupScreenSharingMain(jitsiMeetWindow, appName, osxBundleId);

4. Renderer

Bundle the renderer entry into the app's page code and call the setup*Render helpers with the live JitsiMeetExternalAPI instance:

import { setupScreenSharingRender } from '@jitsi/electron-sdk/renderer';

// api - the JitsiMeetExternalAPI instance created by the page.
setupScreenSharingRender(api);

Usage

The setup* function names and signatures are unchanged from v9 β€” only the entry point you import them from has changed.

Remote Control

Enables remote desktop control during a Jitsi Meet session. Mouse/keyboard events are forwarded over the bridge and executed in the main process by robotjs; the renderer never loads a native module.

Requirements:

  1. Jitsi Meet must be initialized through the iframe API.
  2. setupRemoteControlRender requires the Jitsi Meet iframe API object.

In the main process:

const { setupRemoteControlMain } = require('@jitsi/electron-sdk/main');

// jitsiMeetWindow - the BrowserWindow where Jitsi Meet is loaded.
setupRemoteControlMain(jitsiMeetWindow);

User consent: every session start is gated on an explicit confirmation collected in the main process, because the start request reaches the renderer as an iframe β†’ top-frame postMessage and therefore carries no trustworthy identity. By default a native, modal message box parented to jitsiMeetWindow is shown; web content can neither render, click nor dismiss it. Pass requestConsent to provide your own wording (for instance a localized dialog); it receives { sourceId } and must resolve to true only when the user explicitly allowed the session. Whatever you supply must not be renderable or dismissable by web content β€” a prompt inside the meeting page is not a consent gate.

setupRemoteControlMain(jitsiMeetWindow, {
    async requestConsent({ sourceId }) { // eslint-disable-line no-unused-vars
        const { response } = await dialog.showMessageBox(jitsiMeetWindow, {
            type: 'warning',
            buttons: [ t('remoteControl.deny'), t('remoteControl.allow') ],
            defaultId: 0,
            cancelId: 0,
            message: t('remoteControl.message'),
            detail: t('remoteControl.detail')
        });

        return response === 1;
    }
});

Passing requestConsent: false disables the gate: every requested session starts, with no prompt and no interaction.

// Starts remote control sessions unconditionally. Read the warning below first.
setupRemoteControlMain(jitsiMeetWindow, { requestConsent: false });

Warning

Only do this when you can guarantee that a start request cannot originate from untrusted web content β€” a kiosk or support appliance that loads one deployment you control, and that has already obtained consent out of band.

In the renderer (page hosting Jitsi Meet):

import { setupRemoteControlRender } from '@jitsi/electron-sdk/renderer';

// api - the Jitsi Meet iframe api object.
const remoteControl = setupRemoteControlRender(api);

To disable remote control:

remoteControl.dispose();

NOTE: dispose is called automatically on the Jitsi Meet API readyToClose event or when the iframe API's own dispose method runs.

Screen Sharing

Custom screen/window picker plus an always-on-top "X is sharing your screen" tracker window.

In the main process:

const { setupScreenSharingMain } = require('@jitsi/electron-sdk/main');

// jitsiMeetWindow - the BrowserWindow where Jitsi Meet is loaded.
// appName     - shown in the tracker window: "{appName} is sharing your screen".
// osxBundleId - macOS bundle id; screen-capture permissions are reset if the user denied them.
setupScreenSharingMain(jitsiMeetWindow, appName, osxBundleId);

In the renderer:

import { setupScreenSharingRender } from '@jitsi/electron-sdk/renderer';

// api             - the Jitsi Meet iframe api object.
// loggerTransports - optional array of @jitsi/logger transports.
setupScreenSharingRender(api, loggerTransports);

Picture in Picture

Enables the browser's native picture-in-picture for the active speaker video, so users can keep it in a floating window while using other applications.

Requirements:

  1. Jitsi Meet must be initialized through the iframe API.
  2. The main process executes the PiP request with userGesture privileges to bypass browser transient-activation restrictions.

In the main process:

const { setupPictureInPictureMain } = require('@jitsi/electron-sdk/main');

// jitsiMeetWindow  - the BrowserWindow where Jitsi Meet is loaded.
// loggerTransports - optional array of @jitsi/logger transports.
const pipMain = setupPictureInPictureMain(jitsiMeetWindow, loggerTransports);

In the renderer:

import { setupPictureInPictureRender } from '@jitsi/electron-sdk/renderer';

// api              - the JitsiMeetExternalAPI instance.
// loggerTransports - optional array of @jitsi/logger transports.
const pipRender = setupPictureInPictureRender(api, loggerTransports);

Power Monitor

Query Electron for system idle state and receive power-monitor events (suspend, resume, lock, unlock).

In the main process:

const { setupPowerMonitorMain, cleanupPowerMonitorMain } = require('@jitsi/electron-sdk/main');

// jitsiMeetWindow - the BrowserWindow where Jitsi Meet is loaded.
setupPowerMonitorMain(jitsiMeetWindow);

// On shutdown, tear down all power-monitor hooks:
// cleanupPowerMonitorMain();

In the renderer:

import { setupPowerMonitorRender } from '@jitsi/electron-sdk/renderer';

setupPowerMonitorRender(api);

Popups Configuration

Configures handling of popup windows for OAuth authentication flows (Google, Dropbox). It sets a setWindowOpenHandler on the Jitsi Meet window that allows OAuth popups and delegates all other window.open requests to a handler you provide.

In the main process:

const { shell } = require('electron');
const { initPopupsConfigurationMain } = require('@jitsi/electron-sdk/main');

// Called for window.open requests that are not OAuth popups.
const windowOpenHandler = ({ url }) => {
    shell.openExternal(url); // open external links in the default browser
    return { action: 'deny' };
};

// jitsiMeetWindow    - the BrowserWindow where Jitsi Meet is loaded.
// windowOpenHandler  - optional; if omitted, non-OAuth window.open requests are denied.
initPopupsConfigurationMain(jitsiMeetWindow, windowOpenHandler);

initPopupsConfigurationRender(api) is exported from the renderer entry for API compatibility but is a no-op.

Migrating from v9

v10 is a breaking release. The public API functions are the same; what changed is how you load them and how the window is configured.

  • The default entry is gone. require('@jitsi/electron-sdk') no longer resolves. Import from @jitsi/electron-sdk/main, @jitsi/electron-sdk/preload, or @jitsi/electron-sdk/renderer depending on where the code runs.
  • The Jitsi Meet window must use contextIsolation: true. Because robotjs left the renderer, that window can also run with sandbox: true.
  • Install the bridge from the preload. The app's (bundled) preload must call install() from @jitsi/electron-sdk/preload. Renderer code no longer receives ipcRenderer and must not assign the SDK helpers onto window itself.
  • Move setup*Render calls into the app's renderer bundle and call them directly with the JitsiMeetExternalAPI instance (instead of via a window.* object bridged from the preload). The signatures are unchanged.

Apps that must stay on contextIsolation: false can pin to @jitsi/electron-sdk@9.

Example

For a full integration example see the Jitsi Meet Electron project.

Development

Enable husky to avoid accidental pushes to the main branch:

npx husky install

To rebuild the native code (Windows), use:

npx node-gyp rebuild

Publishing

On every push to the master branch, .github/workflows/ci.yml creates a new patch version and publishes to npm. For a major or minor release, manually bump the version in package.json gh-action-bump-version workflow.

About

Jitsi Meet SDK for Electron

Topics

Resources

Stars

82 stars

Watchers

16 watching

Forks

Releases

Packages

Used by

Contributors

Languages