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

@ermis-network/ermis-classroom-sdk

v2.3.36

Published

TypeScript SDK for Ermis Classroom - Real-time video conferencing and collaboration

Downloads

914

Readme

@ermis-network/ermis-classroom-sdk

TypeScript SDK for Ermis Classroom - Real-time video conferencing and collaboration platform.

Version TypeScript License

Table of Contents

Features

  • 🎥 Real-time Video/Audio Streaming - Support for 360p and 720p with modern codecs
  • 🖥️ Screen Sharing - Screen share with audio support (720p/1080p)
  • 🎙️ Audio Processing with WebCodecs - Opus audio codec at 48kHz
  • 📡 Multiple Transport Protocols - WebRTC, WebTransport, WebSocket (auto-selects based on browser)
  • 👥 Participant Management - Track and manage participants with different roles
  • 🏠 Breakout Rooms - Create and manage breakout rooms within meetings
  • 💬 Real-time Chat - Send messages, typing indicators, reactions
  • Raise Hand - Hand raise feature for meetings
  • 📌 Pin Participant - Pin participant video
  • 🔊 WASM-powered Performance - Uses WebAssembly for optimal performance
  • 📦 Self-contained - Bundled with all runtime dependencies

Packages

This SDK consists of two packages:

| Package | Description | |---------|-------------| | @ermis-network/ermis-classroom-sdk | Core SDK - works with any framework or vanilla JS/TS | | @ermis-network/ermis-classroom-react | React bindings - hooks and components for React apps |

System Requirements

  • Node.js: >= 18
  • Browser: Chrome 90+, Firefox 90+, Safari 15+, Edge 90+
  • TypeScript: >= 5.0 (recommended)

Installation

For React Projects (Recommended)

npm install @ermis-network/ermis-classroom-sdk @ermis-network/ermis-classroom-react

For Vanilla JS/TS Projects

npm install @ermis-network/ermis-classroom-sdk

Static Files Setup

The SDK includes static files (workers, WASM modules, polyfills) in node_modules after installation. However, these files need to be served as static HTTP resources because:

  • Web Workers cannot be bundled into JavaScript bundles - they must be loaded via URL
  • WASM modules require HTTP serving for proper initialization
  • Browser security policies require workers to be loaded from the same origin

For Vite Projects (Recommended)

Use the built-in Vite plugin to automatically copy static files:

// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { copySDKStaticFiles } from '@ermis-network/ermis-classroom-sdk/vite-plugin';

export default defineConfig({
  plugins: [
    react(),
    copySDKStaticFiles({ verbose: true }),
  ],
});

For Other Build Tools

Manually copy the files to your static/public folder:

cp -r node_modules/@ermis-network/ermis-classroom-sdk/src/workers public/
cp -r node_modules/@ermis-network/ermis-classroom-sdk/src/raptorQ public/
cp -r node_modules/@ermis-network/ermis-classroom-sdk/src/polyfills public/
cp -r node_modules/@ermis-network/ermis-classroom-sdk/src/opus_decoder public/

Usage Flow (React)

This section follows the exact flow used in the demo app. The flow consists of 6 steps:

┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│   Setup     │────▶│   Auth      │────▶│  Pre-Join   │
│  Provider   │     │   Screen    │     │   Screen    │
└─────────────┘     └─────────────┘     └──────┬──────┘
                                               │
┌─────────────┐     ┌─────────────┐            ▼
│   Leave     │◀────│  Meeting    │◀───────────┘
│   Room      │     │   Room      │
└─────────────┘     └─────────────┘

Step 1: Setup Provider

Wrap your app with ErmisClassroomProvider:

// App.tsx
import { ErmisClassroomProvider } from '@ermis-network/ermis-classroom-react';

function App() {
  return (
    <ErmisClassroomProvider
      config={{
        host: 'api.ermis.network',           // API host
        hostNode: 'media.ermis.network',     // Media server node
        webtpUrl: 'https://media.ermis.network/meeting/wt', // WebTransport URL
      }}
    >
      <YourApp />
    </ErmisClassroomProvider>
  );
}

Step 2: Authentication

Use useErmisClassroom hook to authenticate users:

// AuthScreen.tsx
import { useState } from 'react';
import { useErmisClassroom } from '@ermis-network/ermis-classroom-react';

function AuthScreen({ onAuthenticated }: { onAuthenticated: () => void }) {
  const [userId, setUserId] = useState('');
  const [isLoading, setIsLoading] = useState(false);
  const { authenticate } = useErmisClassroom();

  const handleAuthenticate = async () => {
    setIsLoading(true);
    try {
      await authenticate(userId);
      onAuthenticated();
    } catch (err) {
      console.error('Authentication failed:', err);
    } finally {
      setIsLoading(false);
    }
  };

  return (
    <div>
      <input
        value={userId}
        onChange={(e) => setUserId(e.target.value)}
        placeholder="Enter your user ID"
      />
      <button onClick={handleAuthenticate} disabled={isLoading}>
        {isLoading ? 'Authenticating...' : 'Authenticate'}
      </button>
    </div>
  );
}

Step 3: Pre-Join (Setup Devices)

Setup camera/microphone preview and select devices before joining:

// PreJoinScreen.tsx
import { useState, useEffect, useRef } from 'react';
import { useErmisClassroom } from '@ermis-network/ermis-classroom-react';

function PreJoinScreen({ onJoined }: { onJoined: () => void }) {
  const [roomCode, setRoomCode] = useState('');
  const videoRef = useRef<HTMLVideoElement>(null);

  const {
    devices,              // { cameras, microphones, speakers }
    selectedDevices,      // { camera, microphone, speaker }
    switchCamera,         // (deviceId) => void
    switchMicrophone,     // (deviceId) => void
    getPreviewStream,     // () => Promise<MediaStream>
    stopPreviewStream,    // () => void
    previewStream,        // MediaStream | null
    joinRoom,             // (roomCode, stream?) => Promise<void>
  } = useErmisClassroom();

  // Start camera preview on mount
  useEffect(() => {
    getPreviewStream();
    return () => stopPreviewStream();
  }, []);

  // Attach stream to video element
  useEffect(() => {
    if (videoRef.current && previewStream) {
      videoRef.current.srcObject = previewStream;
    }
  }, [previewStream]);

  const handleJoinRoom = async () => {
    await joinRoom(roomCode, previewStream ?? undefined);
    onJoined();
  };

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

      {/* Device Selection */}
      <select
        value={selectedDevices?.camera || ''}
        onChange={(e) => switchCamera(e.target.value)}
      >
        {devices?.cameras?.map((camera) => (
          <option key={camera.deviceId} value={camera.deviceId}>
            {camera.label}
          </option>
        ))}
      </select>

      <select
        value={selectedDevices?.microphone || ''}
        onChange={(e) => switchMicrophone(e.target.value)}
      >
        {devices?.microphones?.map((mic) => (
          <option key={mic.deviceId} value={mic.deviceId}>
            {mic.label}
          </option>
        ))}
      </select>

      {/* Room Code & Join */}
      <input
        value={roomCode}
        onChange={(e) => setRoomCode(e.target.value)}
        placeholder="Enter room code"
      />
      <button onClick={handleJoinRoom}>Join Room</button>
    </div>
  );
}

Step 4: Join Room

The joinRoom function connects you to the meeting and starts streaming:

// The joinRoom call in PreJoinScreen above does the following:
// 1. Connects to the room via the room code
// 2. Sets up Publisher (to send your video/audio)
// 3. Sets up Subscribers (to receive remote participants' streams)
// 4. Emits 'localStreamReady' and 'remoteStreamReady' events

await joinRoom(roomCode, previewStream);

Step 5: Meeting Room

Display participants, handle media controls, and screen sharing:

// MeetingRoom.tsx
import { useEffect, useRef, useMemo } from 'react';
import { useErmisClassroom, useMediaDevices } from '@ermis-network/ermis-classroom-react';

function MeetingRoom({ onLeft }: { onLeft: () => void }) {
  const {
    // Participants
    participants,           // Map<string, Participant>
    userId,                 // Current user ID
    
    // Streams
    localStream,            // MediaStream | null
    remoteStreams,          // Map<userId, MediaStream>
    
    // Media state
    micEnabled,             // boolean
    videoEnabled,           // boolean
    handRaised,             // boolean
    isScreenSharing,        // boolean
    screenShareStreams,     // Map<id, { stream, userName }>
    
    // Controls
    toggleMicrophone,       // () => void
    toggleCamera,           // () => void
    toggleRaiseHand,        // () => void
    toggleScreenShare,      // () => void
    togglePin,              // (userId, type) => void
    leaveRoom,              // () => Promise<void>
    
    // Room info
    currentRoom,            // Room | null
  } = useErmisClassroom();

  const {
    microphones,
    cameras,
    selectedMicrophone,
    selectedCamera,
    selectMicrophone,
    selectCamera,
  } = useMediaDevices();

  // Get participant list
  const participantList = useMemo(() => {
    return Array.from(participants.values());
  }, [participants]);

  // Leave room handler
  const handleLeave = async () => {
    await leaveRoom();
    onLeft();
  };

  return (
    <div>
      {/* Local Video */}
      <LocalVideo stream={localStream} />

      {/* Remote Participants */}
      {participantList
        .filter((p) => p.userId !== userId)
        .map((participant) => (
          <RemoteVideo
            key={participant.userId}
            participant={participant}
            stream={remoteStreams.get(participant.userId)}
          />
        ))}

      {/* Screen Shares */}
      {Array.from(screenShareStreams.entries()).map(([id, data]) => (
        <ScreenShareVideo key={id} stream={data.stream} userName={data.userName} />
      ))}

      {/* Controls */}
      <div className="controls">
        <button onClick={toggleMicrophone}>
          {micEnabled ? '🎤' : '🔇'}
        </button>
        <button onClick={toggleCamera}>
          {videoEnabled ? '📹' : '📷'}
        </button>
        <button onClick={toggleRaiseHand}>
          {handRaised ? '✋' : '🤚'}
        </button>
        <button onClick={toggleScreenShare}>
          {isScreenSharing ? 'Stop Share' : 'Share Screen'}
        </button>
        <button onClick={handleLeave}>Leave</button>

        {/* Device Selection */}
        <select
          value={selectedMicrophone || ''}
          onChange={(e) => selectMicrophone(e.target.value)}
        >
          {microphones.map((mic) => (
            <option key={mic.deviceId} value={mic.deviceId}>
              {mic.label}
            </option>
          ))}
        </select>
      </div>
    </div>
  );
}

// Helper components
function LocalVideo({ stream }: { stream: MediaStream | null }) {
  const videoRef = useRef<HTMLVideoElement>(null);
  
  useEffect(() => {
    if (videoRef.current && stream) {
      videoRef.current.srcObject = stream;
    }
  }, [stream]);

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

function RemoteVideo({ participant, stream }: { participant: any; stream?: MediaStream }) {
  const videoRef = useRef<HTMLVideoElement>(null);
  
  useEffect(() => {
    if (videoRef.current && stream) {
      videoRef.current.srcObject = stream;
    }
  }, [stream]);

  return (
    <div>
      <video ref={videoRef} autoPlay playsInline />
      <span>{participant.userId}</span>
      {participant.isHandRaised && <span>✋</span>}
      {!participant.isAudioEnabled && <span>🔇</span>}
    </div>
  );
}

Step 6: Leave Room

const handleLeave = async () => {
  await leaveRoom();
  // Navigate back to pre-join or auth screen
  onLeft();
};

Core SDK Usage (Vanilla JS/TS)

If not using React, you can use the core SDK directly:

import { ErmisClient, ROOM_EVENTS } from '@ermis-network/ermis-classroom-sdk';

// 1. Create client
const client = new ErmisClient({
  host: 'api.ermis.network',
  hostNode: 'media.ermis.network',
  webtpUrl: 'https://media.ermis.network/meeting/wt',
  debug: true,
});

// 2. Authenticate
await client.authenticate('user-123');

// 3. Setup event listeners
client.on(ROOM_EVENTS.LOCAL_STREAM_READY, ({ stream }) => {
  document.getElementById('local-video').srcObject = stream;
});

client.on(ROOM_EVENTS.REMOTE_STREAM_READY, ({ stream, participant }) => {
  const video = document.createElement('video');
  video.srcObject = stream;
  video.autoplay = true;
  document.getElementById('remote-videos').appendChild(video);
});

client.on(ROOM_EVENTS.PARTICIPANT_ADDED, ({ participant }) => {
  console.log('New participant:', participant.userId);
});

// 4. Get media stream
const mediaStream = await navigator.mediaDevices.getUserMedia({
  video: { width: 1280, height: 720 },
  audio: true,
});

// 5. Join room
const { room, localParticipant, participants } = await client.joinRoom('ROOM-CODE', mediaStream);

// 6. Control media
await localParticipant.toggleMicrophone();
await localParticipant.toggleCamera();
await localParticipant.toggleRaiseHand();

// 7. Screen share
await room.startScreenShare();
await room.stopScreenShare();

// 8. Leave room
await client.leaveRoom();
await client.logout();

API Reference

React Hooks

| Hook | Description | |------|-------------| | useErmisClassroom() | Main hook - access all SDK functionality | | useParticipants() | Get all participants in the room | | useLocalParticipant() | Get local participant info | | useRemoteParticipants() | Get remote participants only | | useMediaDevices() | Manage camera/microphone devices | | useLocalMedia() | Control local media (mic, camera) | | usePreviewStream() | Manage preview stream before joining | | useScreenShare() | Control screen sharing | | useRoom() | Access room information and actions | | useRemoteStreams() | Get all remote media streams | | useRemoteStream(userId) | Get specific participant's stream | | usePinState() | Manage pinned participant state | | useCustomEvents() | Send/receive custom events |

React Components

| Component | Description | |-----------|-------------| | ErmisClassroomProvider | Context provider - wraps your app | | GridLayout | Grid layout for participants | | FocusLayout | Focus layout with main + sidebar | | CarouselLayout | Carousel layout for participants | | ParticipantTile | Render a participant tile | | ScreenShareTile | Render a screen share tile |

Core SDK Classes

| Class | Description | |-------|-------------| | ErmisClient | Main client - auth, room management | | Room | Room instance - participants, chat, media | | Participant | Participant - media controls, info | | Publisher | Handle outgoing media streams | | Subscriber | Handle incoming media streams | | MediaDeviceManager | Manage devices |

For detailed API documentation, see:


Events

Room Events

import { ROOM_EVENTS } from '@ermis-network/ermis-classroom-sdk';

// Lifecycle
ROOM_EVENTS.JOINING                    // Room joining
ROOM_EVENTS.JOINED                     // Room joined
ROOM_EVENTS.LEAVING                    // Room leaving
ROOM_EVENTS.LEFT                       // Room left
ROOM_EVENTS.ERROR                      // Error occurred

// Participants
ROOM_EVENTS.PARTICIPANT_ADDED          // New participant
ROOM_EVENTS.PARTICIPANT_REMOVED        // Participant left
ROOM_EVENTS.PARTICIPANT_DISCONNECTED   // Participant disconnected
ROOM_EVENTS.PARTICIPANT_RECONNECTED    // Participant reconnected

// Streams
ROOM_EVENTS.LOCAL_STREAM_READY         // Local stream ready
ROOM_EVENTS.REMOTE_STREAM_READY        // Remote stream ready
ROOM_EVENTS.REMOTE_SCREEN_SHARE_STREAM_READY // Screen share stream ready

// Screen Share
ROOM_EVENTS.SCREEN_SHARE_STARTED       // Screen share started
ROOM_EVENTS.SCREEN_SHARE_STOPPED       // Screen share stopped
ROOM_EVENTS.REMOTE_SCREEN_SHARE_STARTED // Remote screen share started
ROOM_EVENTS.REMOTE_SCREEN_SHARE_STOPPED // Remote screen share stopped

// Media Status
ROOM_EVENTS.AUDIO_TOGGLED              // Audio toggled
ROOM_EVENTS.VIDEO_TOGGLED              // Video toggled
ROOM_EVENTS.HAND_RAISE_TOGGLED         // Hand raise toggled
ROOM_EVENTS.REMOTE_AUDIO_STATUS_CHANGED // Remote audio changed
ROOM_EVENTS.REMOTE_VIDEO_STATUS_CHANGED // Remote video changed

// Chat
ROOM_EVENTS.MESSAGE_RECEIVED           // Message received
ROOM_EVENTS.MESSAGE_SENT               // Message sent
ROOM_EVENTS.TYPING_STARTED             // User started typing
ROOM_EVENTS.TYPING_STOPPED             // User stopped typing

Types & Constants

import {
  // Constants
  RoomTypes,         // { MAIN, SUB, BREAKOUT, PRIVATE }
  StreamTypes,       // { CAMERA, SCREEN_SHARE }
  ParticipantRoles,  // { HOST, CO_HOST, PARTICIPANT, VIEWER }
  ConnectionStatus,  // { DISCONNECTED, CONNECTING, CONNECTED, FAILED }
  ROOM_EVENTS,
  MEETING_EVENTS,
  VERSION,
} from '@ermis-network/ermis-classroom-sdk';

import type {
  // Types
  ErmisClientConfig,
  RoomConfig,
  RoomInfo,
  RoomType,
  JoinRoomResult,
  ParticipantInfo,
  ParticipantRole,
  ChatMessage,
  CustomEventData,
} from '@ermis-network/ermis-classroom-sdk';

Debugging

The SDK uses the debug library for logging. To enable debug logs in the browser console:

Enable Debug Logs

Chrome / Edge / Firefox

  1. Open DevTools Console (F12 or Ctrl+Shift+I)
  2. Run this command:
localStorage.setItem('debug', 'ermis-classroom-sdk*');
  1. Hard refresh the page: Ctrl+Shift+R (Windows/Linux) or Cmd+Shift+R (Mac)

Safari

Safari requires a page reload after setting localStorage:

localStorage.setItem('debug', 'ermis-classroom-sdk*');
location.reload();

Disable Debug Logs

To disable debug logs:

localStorage.removeItem('debug');
location.reload();

Debug Namespaces

You can filter logs by specific namespaces:

// All SDK logs
localStorage.setItem('debug', 'ermis-classroom-sdk*');

// Only Publisher logs
localStorage.setItem('debug', 'ermis-classroom-sdk:publisher*');

// Only Subscriber logs
localStorage.setItem('debug', 'ermis-classroom-sdk:subscriber*');

// Multiple namespaces
localStorage.setItem('debug', 'ermis-classroom-sdk:publisher*,ermis-classroom-sdk:room*');

Programmatic Debug Control

You can also enable debug mode via the SDK config:

const client = new ErmisClient({
  host: 'api.ermis.network',
  debug: true, // Enable debug mode
});

// Or toggle at runtime
client.enableDebug();
client.disableDebug();

Browser Support

| Browser | Version | Transport Protocol | |---------|---------|-------------------| | Chrome | 90+ | WebTransport (preferred), WebRTC | | Firefox | 90+ | WebRTC, WebSocket | | Safari | 15+ | WebSocket, WebRTC | | Edge | 90+ | WebTransport (preferred), WebRTC |

Note: Safari uses WebSocket as the default protocol due to WebTransport limitations. The SDK automatically detects and selects the appropriate protocol.


License

MIT © Ermis Network

Support