nimeet Docs
中文

Electron SDK Guide

Electron main/renderer split, screen capture and packaging notes.

Interface sections generated from the public SDK definitions

Version v1.1.0 · Updated 2026-10-26 · revision 1

The Electron platform is the Web SDK public surface plus a desktop capture bridge. The renderer still uses the AgoraRTC-shaped API from @nimeet/rtc-web; the desktop side additionally provides "enumerate window/screen sources" and "source-pinned screen sharing", because the browser's getDisplayMedia cannot target one specific window.

Prerequisites

  • Node.js ≥ 22 and pnpm 10; Electron ≥ 28 (all three of main process, preload and renderer need changes).
  • contextIsolation: true (the default). This SDK's desktop bridge is injected through the preload script and does not rely on nodeIntegration.
  • You already have an appId and a backend that can issue tokens (see Enable services and get credentials).

Install

bash
pnpm add @nimeet/sdk-electron @nimeet/rtc-web

The three entry points are used in three places: @nimeet/sdk-electron/main (main process), @nimeet/sdk-electron/preload (preload) and @nimeet/sdk-electron (renderer / shared utilities).

Get a token

Exactly as on Web: tokens are issued by your backend calling POST /v1/tokens, and the renderer only receives { appId, rtcToken, rtcUid }. The main process must not hold customerSecret either — it would show up in crash dumps and in user-readable files.

Join a channel

The main process registers the desktop bridge:

javascript
// main.js (main process)
const { app, BrowserWindow, ipcMain, desktopCapturer } = require('electron');
const { registerDesktopBridge } = require('@nimeet/sdk-electron/main');

app.whenReady().then(() => {
  registerDesktopBridge({ ipcMain, desktopCapturer });
});

The preload script exposes the minimal surface:

javascript
// preload.js (preload)
const { contextBridge, ipcRenderer } = require('electron');
const { exposeDesktopBridge } = require('@nimeet/sdk-electron/preload');

exposeDesktopBridge(contextBridge, ipcRenderer);

In the renderer, join with the Web-shaped client (the same methods as the Web SDK):

javascript
import AgoraRTC from '@nimeet/rtc-web';

const client = AgoraRTC.createClient({ mode: 'rtc', codec: 'vp8' });
client.on('user-published', async (user, mediaType) => {
  await client.subscribe(user, mediaType);
  if (mediaType === 'video') user.videoTrack?.play('remote-player');
});
const uid = await client.join(appId, 'weekly-sync', rtcToken, rtcUid);

Publish and subscribe

Camera and microphone work as on Web; screen sharing uses a "source" instead of the default picker:

javascript
import { pickPrimarySource, groupSources } from '@nimeet/sdk-electron';

// window.nimeetDesktop is injected by the preload script
const sources = await window.nimeetDesktop.getSources({ types: ['screen', 'window'] });
const groups = groupSources(sources);          // group by "screen / window" for the dropdown
const primary = pickPrimarySource(sources);    // the primary screen, used as the default

const screenTrack = await AgoraRTC.createScreenVideoTrack(
  { electronScreenSourceId: primary.id },
  'disable',
);
await client.publish(screenTrack);

electronScreenSourceId is the "pinned source" (the field name comes from ScreenVideoTrackInitConfig): it does not change while the user switches windows — the most important difference between the desktop platform and the browser's getDisplayMedia.

Events

The same event set as Web (user-joined / user-published / connection-state-change / token-privilege-will-expire / network-quality…), plus two extra channel events provided by the desktop bridge (prefix nimeet:desktop:):

EventDirectionPurpose
nimeet:desktop:sourcesrenderer ← mainReturns the list of screen/window sources
nimeet:desktop:capture-permissionrenderer ← mainmacOS screen-recording permission state (Chromium does not expose it)

Error handling

There are three common error sources, and they are best handled separately:

  1. RTC errors: the same AgoraRTCError as Web (a code string);
  2. IPC errors: the main process did not register the bridge → window.nimeetDesktop is undefined in the renderer; the preload was not injected → same as above;
  3. System permissions: when macOS "Screen Recording" is not granted, the source list is empty rather than raising an error.
javascript
const sources = await window.nimeetDesktop?.getSources({ types: ['screen'] }) ?? [];
if (sources.length === 0) {
  // macOS: guide the user to System Settings → Privacy & Security → Screen Recording, tick this app and restart
}

Troubleshooting

SymptomCauseHandling
window.nimeetDesktop is undefinedThe preload did not run, or contextIsolation is misconfiguredCheck the webPreferences.preload path and whether the file exists after packaging
The source list is empty (macOS)Screen-recording permission was not grantedGrant it in System Settings and restart the app (permission changes do not apply live)
Sharing shows a black screenThe selected window is protected (for example a DRM player)Switch to the whole-screen source
The renderer reports require is not definedCommonJS syntax was usedWrite the renderer as ESM, or let the bundler handle it
After packaging, IPC reports No handler registeredThe code that registers the bridge was tree-shaken out of the main processCall registerDesktopBridge explicitly in the main-process entry point

Next steps

Platform information

Itemvalue
PlatformElectron(desktop)
LanguageTypeScript
SDK package@nimeet/sdk-electron
Interface version4.6.2
Compatible Agora APIAgora electron API

The public interfaces are aligned with the Agora electron API; installation and runtime requirements are listed under Prerequisites above.

Availability

This platform's SDK: Generally available. The examples use the public API; runtime capability follows the published SDK package.

join-channel flow

The method names, parameter types and return values match the API reference for the same platform. Where a method has several overloads, the most common one is shown here; see the API reference for all overloads.

StepInterfaceMethodOverload count
subscribe to remote mediaIBaseSpatialAudioEnginemuteRemoteAudioStream1
Create instance / initializeILocalSpatialAudioEngineinitialize1
Renew the TokenIMusicContentCenterrenewToken1
enable video and local previewIRtcEngineenableVideo1
Join a channelIRtcEnginejoinChannel1
Leave a channelIRtcEngineleaveChannel1
Channel scenariosIRtcEnginesetChannelProfile1
switch rolesIRtcEnginesetClientRole1
bind the local videoIRtcEnginesetupLocalVideo1
typescript
muteRemoteAudioStream(uid: number, mute: boolean): number
initialize(): number
renewToken(token: string): number
enableVideo(): number
joinChannel(token: string, channelId: string, uid: number, options: ChannelMediaOptions): number
leaveChannel(options?: LeaveChannelOptions): number
setChannelProfile(profile: ChannelProfileType): number
setClientRole(role: ClientRoleType, options?: ClientRoleOptions): number
setupLocalVideo(canvas: VideoCanvas): number

Events and callbacks

Callbacks are registered on IRtcEngineEventHandler; naming differs by platform, and the full list is in the API reference for the same platform.

CallbacksSignaturePurpose
onConnectionStateChangedonConnectionStateChanged(connection: RtcConnection, state: ConnectionStateType, reason: ConnectionChangedReasonType): voidjoin result / connection-state changes:update the UI accordingly and decide whether to reconnect.
onUserJoinedonUserJoined(connection: RtcConnection, remoteUid: number, elapsed: number): voidRemote user joined:allocate the frame container.
onFirstRemoteVideoDecodedonFirstRemoteVideoDecoded(connection: RtcConnection, remoteUid: number, width: number, height: number, elapsed: number): voidRemote video status:it can render once the first frame is decoded.
onUserOfflineonUserOffline(connection: RtcConnection, remoteUid: number, reason: UserOfflineReasonType): voidRemote user left:release the container and subscription.
onRequestTokenonRequestToken(connection: RtcConnection): voidToken expiring soon:call the server to renew, then `renewToken`.
onErroronError(err: ErrorCodeType, msg: string): voidRuntime errors:`err` is an Agora error number, route by the error-code table.
onNetworkQualityonNetworkQuality(connection: RtcConnection, remoteUid: number, txQuality: QualityType, rxQuality: QualityType): voidNetwork quality:report to Analytics or prompt the user.

Error codes

Constantvalue
ErrAborted20
ErrAdmGeneralError1005
ErrAdmInitPlayout1008
ErrAdmInitRecording1011
ErrAdmStartPlayout1009
ErrAdmStartRecording1012
ErrAdmStopPlayout1010
ErrAdmStopRecording1013
ErrAlreadyInUse19
ErrBitrateLimit115
ErrBufferTooSmall6
ErrCertRaw1001
ErrClientIsBannedByServer123
ErrConnectionInterrupted111
ErrConnectionLost112
ErrDatastreamDecryptionFailed122
ErrDecryptionFailed120
ErrEncryptedStreamNotAllowedPublish130
ErrFailed1
ErrInitNetEngine21
ErrInvalidAppId101
ErrInvalidArgument2
ErrInvalidChannelName102
ErrInvalidState8
ErrInvalidTokennull
ErrInvalidUserAccount134
ErrInvalidUserId121
ErrJoinChannelRejected17
ErrLeaveChannelRejected18
ErrNoPermission9
ErrNoServerResources103
ErrNotInChannel113
ErrNotInitialized7
ErrNotReady3
ErrNotSupported4
ErrOk0
ErrPcmsendBufferoverflow201
ErrPcmsendFormat200
ErrRefused5
ErrResourceLimited22
ErrSetClientRoleNotAuthorized119
ErrSizeTooLarge114
ErrStreamMessageTimeout117
ErrTimedout10
ErrTokenExpired109
ErrTooManyDataStreams116
ErrVdmCameraNotAuthorized1501
IRtcEnginenull

the platform REST layer uses REST error codes;the SDK layer maps connection/authentication failures to Agora error numbers, for the mapping table see SDK error identifiers per platform。

Generated by the documentation build pipeline · 2026-10-31 01:24:02 UTC · Documentation center v1.1.0.

Content follows the public API definitions.

ni-meet is a self-hosted RTC PaaS with a LiveKit-based media plane (Apache-2.0) and an Agora-compatible API.