npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@defcomm/meetingsdk

v0.6.1

Published

React/TypeScript meeting SDK for the DEFCOMM Rust SFU

Readme

DEFCOMM Meet SDK

@afosecure/meetingsdk is the React/TypeScript client SDK used by DEFCOMM Meet.

It provides the application layer for:

  • joining and leaving meetings
  • microphone and camera control
  • remote participant media
  • screen sharing
  • active-speaker detection
  • participant presence and reconnection state
  • private-room admission
  • server-side recording
  • room management
  • chat/PubSub integration
  • WebRTC and SFU signaling orchestration

The SDK is designed so that a DEFCOMM Meet consumer should not need to manage WebRTC peer connections, SDP, ICE candidates, RTP tracks, reconnect logic, or browser media plumbing directly.


Table of contents

  1. What the SDK does
  2. Architecture
  3. Installation
  4. SDK configuration
  5. Creating a meeting
  6. Joining a meeting
  7. MeetingProvider
  8. The useMeeting hook
  9. Participants
  10. Local participant
  11. Remote media
  12. Microphone and camera
  13. Active speaker detection
  14. Screen sharing
  15. Recording
  16. Private rooms and join approval
  17. Reconnection and participant presence
  18. Leaving a meeting
  19. Room management
  20. Meeting preview
  21. Chat
  22. Error handling
  23. Recommended DEFCOMM Meet structure
  24. Important implementation rules
  25. Build and publish

What the SDK does

DEFCOMM Meet is built around an SFU architecture.

The browser does not establish a separate peer connection with every participant.

Instead:

Participant A ──┐
Participant B ──┼──► DEFCOMM Rust SFU ──► Participants
Participant C ──┘

The SDK sits between the React application and the SFU:

┌───────────────────────────────┐
│        DEFCOMM Meet App       │
│                               │
│   Meeting UI / Participant UI │
└───────────────┬───────────────┘
                │
                ▼
┌───────────────────────────────┐
│     @afosecure/meetingsdk     │
│                               │
│ React hooks                   │
│ Meeting state                 │
│ WebRTC                       │
│ WebSocket signaling           │
│ Reconnection                  │
│ Active speaker detection      │
│ Media attachment              │
│ Recording controls            │
└───────────────┬───────────────┘
                │
                ▼
┌───────────────────────────────┐
│       DEFCOMM Rust SFU        │
│                               │
│ Rooms                         │
│ Participants                  │
│ Admission                     │
│ SDP / ICE                     │
│ RTP forwarding                │
│ Track state                   │
│ Recording                     │
└───────────────────────────────┘

The responsibility split

Your application owns:

  • layout
  • buttons
  • participant tiles
  • navbar
  • chat UI
  • recording indicator
  • active-speaker styling
  • loading/error screens

The SDK owns:

  • browser media access
  • publisher PeerConnection
  • subscriber PeerConnection
  • WebSocket signaling
  • media track mapping
  • reconnect/resume behavior
  • active-speaker detection
  • SDK meeting state

The SFU owns:

  • room state
  • participant membership
  • admission/approval
  • signaling authority
  • media forwarding
  • track publication/subscription
  • server-side recording

Installation

npm install @afosecure/meetingsdk

The SDK is intended for browser-based React + TypeScript applications.


Creating a meeting

The SDK exposes room management through VideoSDKCore and useMeeting().

const { createRoom } = useMeeting();

const room = await createRoom({
  name: "Engineering Standup",
  description: "Daily engineering meeting",
  capacity: 50,
  is_private: true,
});

A room can also be retrieved:

const { getRoom } = useMeeting();

const room = await getRoom(roomCode);

And deleted when appropriate:

const { deleteRoom } = useMeeting();

await deleteRoom(roomCode);

Room creation and deletion should normally be restricted to the appropriate application role.


Joining a meeting

The normal React integration starts with MeetingProvider.

import { MeetingProvider, useMeeting } from "@afosecure/meetingsdk";

function MeetingRoom() {
  const { join, leave } = useMeeting();

  return (
    <div>
      <button onClick={() => join()}>Join meeting</button>

      <button onClick={() => leave()}>Leave</button>
    </div>
  );
}

export function MeetingPage() {
  return (
    <MeetingProvider
      config={{
        roomId: "room-code",
        name: "Afolabi",
        audioMuted: false,
        videoMuted: false,
      }}
    >
      <MeetingRoom />
    </MeetingProvider>
  );
}

MeetingProvider accepts:

type MeetingConfig = {
  roomId: string;
  name: string;
  audioMuted?: boolean;
  videoMuted?: boolean;
  token?: string;
};

Recommended join flow

Meeting page
    ↓
MeetingProvider
    ↓
User clicks Join
    ↓
join()
    ↓
Browser requests camera/microphone
    ↓
SDK connects to SFU
    ↓
Publisher PeerConnection
    ↓
Subscriber PeerConnection
    ↓
Remote tracks become available

Call join() from a user action whenever possible. This gives the browser a normal user-initiated context for requesting media permissions.


The useMeeting hook

useMeeting() is the main application API.

const {
  join,
  leave,

  toggleMic,
  toggleCam,

  startScreenShare,
  stopScreenShare,

  startRecording,
  stopRecording,
  getRecordingStatus,

  participants,
  localParticipant,
  activeSpeakerIds,
  presenterId,
  room,

  approveJoinRequest,
  rejectJoinRequest,

  sendMessage,
} = useMeeting();

The hook also supports event handlers:

const meeting = useMeeting({
  onError: (error) => {
    console.error(error);
  },

  onActiveSpeakersChanged: (participantIds) => {
    console.log("Active speakers:", participantIds);
  },

  onRecordingStarted: (recording) => {
    console.log("Recording started:", recording);
  },

  onRecordingStopped: (recording) => {
    console.log("Recording stopped:", recording);
  },
});

For normal React rendering, prefer the state exposed by the hook:

const { activeSpeakerIds } = useMeeting();

Use event callbacks when the application needs to perform an additional side effect.


Participants

Use useParticipants() when the component only needs the participant list.

import { useParticipants } from "@afosecure/meetingsdk";

function ParticipantList() {
  const participants = useParticipants();

  return (
    <div>
      {participants.map((participant) => (
        <div key={participant.id}>{participant.name}</div>
      ))}
    </div>
  );
}

A participant contains:

type Participant = {
  id: string;
  name?: string;
  isHost?: boolean;
  isLocal?: boolean;
  isPresenter?: boolean;

  connectionState?: "connected" | "reconnecting" | "disconnected";

  media?: {
    stream?: MediaStream | null;
    screenStream?: MediaStream | null;

    cameraTrack?: MediaStreamTrack;
    screenTrack?: MediaStreamTrack;
    audioTrack?: MediaStreamTrack;

    micEnabled: boolean;
    camEnabled: boolean;
    isScreenSharing: boolean;

    remoteScreenStreamId?: string;
    cameraStreamId?: string;
  };
};

Local participant

Use useLocalParticipant() when you need the local participant and local video.

import { useLocalParticipant } from "@afosecure/meetingsdk";

function LocalVideo() {
  const { participant, videoRef } = useLocalParticipant();

  return <video ref={videoRef} autoPlay muted playsInline />;
}

The local participant exposes:

participant?.media?.micEnabled;
participant?.media?.camEnabled;
participant?.media?.isScreenSharing;

Use these values to render local microphone/camera state.


Remote media

Use useRemoteMedia(participantId) for a remote participant.

import { useRemoteMedia } from "@afosecure/meetingsdk";

function RemoteParticipant({ participantId }: { participantId: string }) {
  const { videoRef, audioRef, isCamActive, isMicEnabled } =
    useRemoteMedia(participantId);

  return (
    <div>
      <video ref={videoRef} autoPlay playsInline muted />

      <audio ref={audioRef} autoPlay />

      {!isMicEnabled && <span>Muted</span>}
      {!isCamActive && <span>Camera off</span>}
    </div>
  );
}

Why are video and audio separate?

The SDK attaches the remote MediaStream to both elements:

  • <video> is muted
  • <audio> handles remote audio

This makes media rendering explicit and avoids accidentally routing remote audio through the video element.


Microphone and camera

The SDK exposes:

const { toggleMic, toggleCam } = useMeeting();

Use:

<button onClick={toggleMic}>
  Microphone
</button>

<button onClick={toggleCam}>
  Camera
</button>

The SDK updates the local media track and sends the corresponding media state to the SFU.

Remote participants receive the authoritative track state through the meeting state.

For participant UI, use:

participant.media?.micEnabled;
participant.media?.camEnabled;

rather than trying to infer another participant's mute state from RTP or MediaStreamTrack.muted.


Active speaker detection

DEFCOMM Meet performs active-speaker detection inside the SDK.

The application does not need to use Web Audio APIs, AnalyserNode, audio RMS calculations, timers, or WebRTC statistics.

The SDK exposes:

const { activeSpeakerIds } = useMeeting();

activeSpeakerIds is:

string[]

containing participant IDs currently detected as speaking.

Participant tile

function ParticipantTile({ participant }: { participant: Participant }) {
  const { activeSpeakerIds } = useMeeting();

  const isActiveSpeaker = activeSpeakerIds.includes(participant.id);

  return (
    <div className={isActiveSpeaker ? "ring-2 ring-green-400" : ""}>
      {participant.name}
    </div>
  );
}

Better pattern

For a participant list:

const { activeSpeakerIds } = useMeeting();

{
  participants.map((participant) => (
    <ParticipantTile
      key={participant.id}
      participant={participant}
      isActiveSpeaker={activeSpeakerIds.includes(participant.id)}
    />
  ));
}

The tile should only render the state. It should not implement speaker detection itself.

Active speaker event

The SDK also exposes:

useMeeting({
  onActiveSpeakersChanged: (participantIds) => {
    console.log("Active speakers:", participantIds);
  },
});

The event is useful for analytics or application-level side effects. For normal UI rendering, activeSpeakerIds is preferred.

Multiple speakers

The API intentionally supports multiple active speakers:

activeSpeakerIds: string[]

rather than:

activeSpeakerId: string | null;

This allows the UI to handle overlapping speech naturally.


Screen sharing

Start:

const { startScreenShare } = useMeeting();

await startScreenShare();

Stop:

const { stopScreenShare } = useMeeting();

stopScreenShare();

A participant's screen share is represented separately from their camera:

participant.media?.screenStream;
participant.media?.screenTrack;
participant.media?.isScreenSharing;

Recommended UI

When someone shares their screen:

┌──────────────────────────────────────────┐
│                                          │
│             SCREEN SHARE                 │
│                                          │
│                                          │
└──────────────────────────────────────────┘

 ┌─────────┐ ┌─────────┐ ┌─────────┐
 │ Alice   │ │ Bob     │ │ Charlie │
 └─────────┘ └─────────┘ └─────────┘

Do not treat the screen track as the participant's camera track.


Recording

DEFCOMM Meet recording is server-side.

The browser does not use MediaRecorder to create the meeting recording.

Start recording:

const { startRecording } = useMeeting();

await startRecording();

Stop recording:

const { stopRecording } = useMeeting();

await stopRecording();

Get current recording status:

const { getRecordingStatus } = useMeeting();

const result = await getRecordingStatus();

console.log(result.recording);

A recording has:

type RecordingInfo = {
  recording_id: string;
  started_at?: string;
  stopped_at?: string;
  active?: boolean;
};

Recording events

The SDK exposes:

useMeeting({
  onRecordingStarted: (recording) => {
    console.log("Recording started", recording);
  },

  onRecordingStopped: (recording) => {
    console.log("Recording stopped", recording);
  },
});

Recommended DEFCOMM Meet UI

The meeting application can place the recording control in the meeting navbar:

┌───────────────────────────────────────────────────────────────┐
│ Engineering     🔴 REC                         Chat  People   │
└───────────────────────────────────────────────────────────────┘

Host before recording:

┌───────────────────────────────────────────────────────────────┐
│ Engineering       [ Start Recording ]          Chat  People   │
└───────────────────────────────────────────────────────────────┘

Host while recording:

┌───────────────────────────────────────────────────────────────┐
│ Engineering       🔴 Recording  [ Stop ]       Chat  People   │
└───────────────────────────────────────────────────────────────┘

Other participants should see the recording status but should not automatically receive the host's recording controls.

Important recording rule

Do not assume that clicking the button means recording has started.

The UI should use:

onRecordingStarted;

as the authoritative start event and:

onRecordingStopped;

as the authoritative stop event.

Recommended state:

const [isRecording, setIsRecording] = useState(false);

useMeeting({
  onRecordingStarted: () => {
    setIsRecording(true);
  },

  onRecordingStopped: () => {
    setIsRecording(false);
  },
});

This prevents the UI from claiming that a recording is active when the SFU has not confirmed it.


Private rooms and join approval

Private rooms can require host approval.

A host can listen for entry requests:

useMeeting({
  onEntryRequested: (request) => {
    console.log("Join request:", request);
  },
});

Approve:

const { approveJoinRequest } = useMeeting();

approveJoinRequest(requestId);

Reject:

const { rejectJoinRequest } = useMeeting();

rejectJoinRequest(requestId);

The SDK exposes the approval flow, while the SFU remains authoritative for whether a participant is admitted.


Reconnection and participant presence

The SDK distinguishes a temporary connection problem from an intentional leave.

A participant can be:

"connected";
"reconnecting";
"disconnected";

Recommended UI:

if (participant.connectionState === "reconnecting") {
  return <ReconnectingBadge />;
}

A temporary network interruption should not immediately remove a participant from the UI.

The SDK attempts to reconnect and resume the same logical participant session.

Conceptually:

Network interruption
        ↓
WebSocket disconnect
        ↓
reconnecting
        ↓
refresh/reconnect
        ↓
resume
        ↓
publisher/subscriber recreated
        ↓
media subscriptions restored
        ↓
connected

This is why applications should render reconnecting as a state rather than treating every WebSocket close as a participant leaving.


Stable participant identity

The SDK stores a stable browser participant ID using:

defcomm:participant_id

This prevents a browser reload or temporary reconnect from unnecessarily generating a completely new participant identity.

Room resume state uses:

defcomm:sfu:resume:<roomCode>

The SDK treats temporary network loss differently from an intentional leave().

Applications should use:

await leave();

for an intentional departure instead of manually closing the WebSocket.


Leaving a meeting

Use:

const { leave } = useMeeting();

await leave();

Do not implement the Leave button by doing:

websocket.close();

or by directly destroying the SDK's PeerConnections.

The SDK owns cleanup and the SFU leave lifecycle.


Room management

Room management is available through useMeeting():

const { createRoom, getRoom, deleteRoom } = useMeeting();

Create

await createRoom({
  name: "Product Review",
  description: "Weekly product review",
  capacity: 100,
  is_private: false,
});

Get

await getRoom(roomCode);

Delete

await deleteRoom(roomCode);

Meeting preview

The SDK exports:

import { useMeetingPreview } from "@afosecure/meetingsdk";

Use preview when the application needs a pre-join screen for room information or local setup.

The actual live meeting begins when join() connects the SDK to the SFU.


Chat

The SDK exposes the current chat interface through:

const { sendMessage } = useMeeting();

Send:

sendMessage({
  message: "Hello everyone",
});

The input type is:

type ChatInput = {
  message: string;
  reply_to?: {
    id: string;
    name: string;
  } | null;
  target?: string | null;
};

The SDK also exposes:

usePubSub("SECURE_CHAT");

when an application wants the Pub/Sub style interface.

Example:

const chat = usePubSub("SECURE_CHAT");

chat.publish({
  message: "Hello",
});

Error handling

Use the SDK error event:

useMeeting({
  onError: (error) => {
    console.error(error);
  },
});

Errors contain:

type SDKError = {
  code: string;
  message: string;
  roomId?: string | null;
  participantId?: string;
  raw?: any;
  recoverable?: boolean;
};

A useful UI rule is:

recoverable = true
    ↓
show retry/reconnecting UI

recoverable = false
    ↓
show a blocking error or return to the appropriate screen

Always wrap user-triggered asynchronous operations when appropriate:

async function handleJoin() {
  try {
    await join();
  } catch (error) {
    console.error("Unable to join meeting", error);
  }
}

Recommended DEFCOMM Meet structure

A production DEFCOMM Meet page can be structured like this:

MeetingPage
│
└── MeetingProvider
    │
    ├── MeetingNav
    │   ├── Meeting name
    │   ├── Recording state/control
    │   ├── Chat
    │   └── Participants
    │
    ├── MeetingStage
    │   │
    │   ├── ScreenShareStage
    │   │
    │   └── ParticipantGrid
    │       └── ParticipantTile[]
    │           ├── Video
    │           ├── Name
    │           ├── Mic state
    │           ├── Camera state
    │           ├── Active speaker state
    │           └── Reconnecting state
    │
    └── MeetingControls
        ├── Microphone
        ├── Camera
        ├── Screen share
        └── Leave

The application should consume SDK state rather than recreating WebRTC state locally.


Recommended participant tile

A participant tile should combine the SDK's states:

function ParticipantTile({ participant }: { participant: Participant }) {
  const { activeSpeakerIds } = useMeeting();

  const isSpeaking = activeSpeakerIds.includes(participant.id);

  const isReconnecting = participant.connectionState === "reconnecting";

  const micEnabled = participant.media?.micEnabled ?? false;

  const camEnabled = participant.media?.camEnabled ?? false;

  const isScreenSharing = participant.media?.isScreenSharing ?? false;

  return (
    <div
      className={
        isSpeaking ? "participant-tile active-speaker" : "participant-tile"
      }
    >
      {isReconnecting && <div className="reconnecting">Reconnecting...</div>}

      {/* Remote/local media */}

      <div className="participant-name">{participant.name}</div>

      <div className="participant-status">
        {!micEnabled && <span>Muted</span>}
        {!camEnabled && <span>Camera off</span>}
        {isScreenSharing && <span>Sharing</span>}
      </div>
    </div>
  );
}

The important design principle is:

SDK state → UI

not:

UI → WebRTC internals

Public exports

The SDK currently exports:

// Core
VideoSDKCore;
SFUClient;
MeetingState;
ActiveSpeakerDetector;

// React
MeetingProvider;
useMeetingContext;
useMeeting;
useParticipants;
useLocalParticipant;
useRemoteMedia;
useMeetingPreview;

// Types
Participant;
ChatInput;
RecordingInfo;
MeetingConfig;
SFUConnectionState;
TrackInfo;
ParticipantInfo;

Most DEFCOMM Meet application developers should only need the React API:

import {
  MeetingProvider,
  useMeeting,
  useParticipants,
  useLocalParticipant,
  useRemoteMedia,
  useMeetingPreview,
} from "@afosecure/meetingsdk";

Direct use of VideoSDKCore or SFUClient is intended for advanced integrations.


Important implementation rules

1. Do not build a mesh

Do not create:

Participant A ↔ Participant B
Participant A ↔ Participant C
Participant B ↔ Participant C

The SDK is designed for:

Participants
     │
     ▼
 DEFCOMM SFU
     │
     ▼
Participants

2. Do not manipulate the SDK's PeerConnections

Applications should not directly access:

RTCPeerConnection;
RTCSessionDescription;
RTCIceCandidate;
RTCRtpSender;

for normal meeting functionality.

The SDK owns those objects.


3. Do not infer remote mute state from media silence

Use:

participant.media?.micEnabled;
participant.media?.camEnabled;

The SFU signaling state is authoritative.


4. Do not implement speaker detection in the application

Use:

const { activeSpeakerIds } = useMeeting();

The SDK owns audio analysis and active-speaker state.


5. Do not use browser recording for meeting recording

Use:

await startRecording();
await stopRecording();

Meeting recording is server-side.


6. Do not manually close the WebSocket to leave

Use:

await leave();

This allows the SDK to perform the intended cleanup and leave lifecycle.


7. Do not immediately remove reconnecting participants

Use:

participant.connectionState;

and show:

Reconnecting...

during transient failures.


8. Keep screen share separate from camera

Use:

participant.media.screenStream;
participant.media.screenTrack;
participant.media.isScreenSharing;

instead of replacing the camera stream.


Build and publish

Install dependencies:

npm install

Build:

npm run build

Before publishing a new SDK version, verify:

✓ TypeScript build succeeds
✓ React integration works
✓ Join/leave works
✓ Camera works
✓ Microphone works
✓ Remote audio/video works
✓ Screen sharing works
✓ Active speaker detection works
✓ Reconnection works
✓ Private-room admission works
✓ Recording start/stop works
✓ Recording callbacks fire
✓ Production WSS/API configuration works

DEFCOMM Meet integration principle

The purpose of this SDK is to make the DEFCOMM meeting experience feel like a single integrated platform.

A consumer should be able to think in terms of:

const {
  join,
  leave,
  toggleMic,
  toggleCam,
  startScreenShare,
  stopScreenShare,
  startRecording,
  stopRecording,
  participants,
  activeSpeakerIds,
} = useMeeting();

rather than:

WebSocket messages
SDP offers/answers
ICE candidates
PeerConnections
RTP tracks
SSRCs
audio analysers
reconnect timers
resume tokens

Those implementation details belong inside the SDK/SFU layer.

DEFCOMM Meet owns the meeting experience.
The SDK owns the browser media experience.
The SFU owns the authoritative meeting/media infrastructure.