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

@openvisio/sdk-js

v2.1.0

Published

Official JavaScript SDK for OpenVisio — an open video meeting platform built on LiveKit. Server and browser clients for room management, moderation, recording, and transcription.

Readme

@openvisio/sdk-js

Official JavaScript SDK for OpenVisio — an open video meeting platform built on LiveKit. Server and browser clients with typed methods for room management, participant moderation, recording, and transcription.

npm


TL;DR

  • Your backend uses OpenvisioServer to create rooms, mint LiveKit join tokens, and run moderation / recording / interactions. It holds the API key.
  • Your frontend joins the call. Two patterns:
    • Headless (recommended) — connect to LiveKit directly with the minted token via livekit-client. Render the UI yourself. No iframe, no SSO popup, no third-party cookies.
    • Iframe (alternative) — embed the OpenVisio web app with OpenvisioBrowser. Convenient (you get the full UI) but you inherit cross-origin auth.
  • Authorization is your responsibility. The OpenVisio proxy trusts the API key holder; who may moderate is decided by your backend, using is_administrable on the room as the source of truth.

Architecture

Your frontend ──── Your backend ──── OpenVisio proxy ──── OpenVisio backend
                  (OpenvisioServer)                       LiveKit server
        │                                       │
        └────────── media (WebRTC) ─────────────┘

The SDK ships as one package with two isolated entry points so the API key cannot leak into the browser bundle:

| Import | Runs on | Holds the key | |---|---|---| | @openvisio/sdk-js/server | your backend (Node 18+) | yes | | @openvisio/sdk-js/browser | your frontend | no |

The browser never calls the proxy directly — it calls your backend, which uses the server client.


Installation

npm install @openvisio/sdk-js

Or load the browser bundle directly from a CDN (no build step required):

<script type="module">
  import { OpenvisioBrowser } from 'https://esm.sh/@openvisio/[email protected]/browser';
  // or: https://unpkg.com/@openvisio/[email protected]/dist/browser.js
  // or: https://cdn.jsdelivr.net/npm/@openvisio/[email protected]/dist/browser.js
</script>

Backend — OpenvisioServer

import { OpenvisioServer } from '@openvisio/sdk-js/server';

const openvisio = new OpenvisioServer({
  proxyUrl: 'https://openvisio-proxy.example.com',
  apiKey: process.env.OPENVISIO_API_KEY!,   // never sent to the browser
});

Create or resolve a room

// Host — create the room AS the logged-in user (forward their OIDC token) so they
// become the room owner -> admin. Without a token, the proxy's service account owns it.
const room = await openvisio.createRoom(undefined, userToken);
// { room_name, slug, is_administrable }

// Guest / join — resolve a slug; pass the user token to learn their room role.
const resolved = await openvisio.resolveRoom('aaa-bbbb-ccc', userToken);
// { room_name, slug, is_administrable }

Mint a join token (10-minute TTL)

const { token, livekit_url } = await openvisio.createToken(room.room_name, 'user_42', {
  displayName: 'Alice',
});

createToken powers the headless join below. Generate it right before the user connects, not at room-creation time.

In-meeting actions

// interactions.* — soft, the participant can undo. Returns StatusResponse.
await openvisio.interactions.chat(room.room_name, 'The meeting ends in 5 minutes.');
await openvisio.interactions.raiseHand(room.room_name, 'user_42', true);
await openvisio.interactions.reaction(room.room_name, 'party-popper');

// moderation.* — server-side permission revoke; only another admin call restores it.
await openvisio.moderation.muteMic(room.room_name, 'user_99');
await openvisio.moderation.kick(room.room_name, 'user_99');

// Recording / transcription — user_id must be a live participant. Keep the egress_id.
const { egress_id } = await openvisio.startRecording(room.room_name, 'user_42');
await openvisio.stopRecording(egress_id);

await openvisio.listRooms();                       // active rooms + participant identities
await openvisio.leave(room.room_name, 'user_42');

Full surface: interactions.{muteMic, unmuteMic, disableCamera, enableCamera, raiseHand, reaction, chat}, moderation.{muteMic, unmuteMic, disableCamera, enableCamera, stopScreenShare, kick}, createRoom, resolveRoom, createToken, listRooms, leave, start/stopRecording, start/stopTranscription, health.


Authorization — read this

The OpenVisio proxy authenticates your application with a single API key and trusts it. Authorization (who can kick, mute, record) is enforced by your backend before calling the SDK.

Key principle: authentication ≠ authorization. Being logged in — or being in the call — does not make a user an admin.

The source of truth for "may this user moderate this room?" is the room role, exposed as is_administrable on createRoom / resolveRoom:

// Host: create AS the user -> they own the room -> is_administrable === true.
const room = await openvisio.createRoom(undefined, userToken);
const isAdmin = room.is_administrable;

// Guest joining someone else's room -> is_administrable === false -> no admin powers.
const resolved = await openvisio.resolveRoom(slug, userToken);

// Enforce it in YOUR routes, before touching the proxy:
function requireAdmin(req, res, next) {
  if (!req.session.isAdmin) return res.status(403).json({ error: 'forbidden' });
  next();
}
app.post('/api/openvisio/kick', requireAdmin, (req, res) =>
  openvisio.moderation.kick(/* ... */));

The proxy creates rooms with its own service account by default, which makes the service account the owner — not your user. Forward the user's OIDC token to createRoom so the user becomes the owner and is_administrable is true for them.


Frontend — Headless (recommended)

Your frontend connects to LiveKit directly with the token from your backend and renders the call itself. No iframe, no SSO popup, no SameSite=None cookies. The participant identity is the one you minted, so you always know who is who.

import { Room, RoomEvent } from 'livekit-client';
// or @livekit/components-react for a turnkey <VideoConference /> UI.

// 1. Your backend returns { token, livekit_url } from openvisio.createToken(...).
const { token, livekitUrl } = await fetch('/api/openvisio/token').then((r) => r.json());

// 2. Connect and render.
const room = new Room({ adaptiveStream: true, dynacast: true });
await room.connect(livekitUrl, token);
await room.localParticipant.enableCameraAndMicrophone();

// 3. Self-actions are client-side (do NOT route them through the proxy).
await room.localParticipant.setMicrophoneEnabled(false);
room.localParticipant.publishData(
  new TextEncoder().encode(JSON.stringify({
    type: 'reactionReceived',
    data: { emoji: 'party-popper' },
  })),
  { reliable: true },
);

// 4. Live UI feedback from the room state:
room
  .on(RoomEvent.TrackMuted, (pub, p) => { /* show muted icon */ })
  .on(RoomEvent.ParticipantAttributesChanged, (changed, p) => {
    if (changed.handRaisedAt) { /* show raised hand */ }
  })
  .on(RoomEvent.RoomMetadataChanged, () => {
    /* JSON.parse(room.metadata).recording_status === 'started' -> show REC */
  });

Actions on other participants (moderation, soft mutes, recording) go through the proxy via your backend; self mic/cam/screen and animated reactions are done client-side on room.localParticipant. See the openvisio-sdk-js-sample app for a full working reference.


Frontend — Iframe (alternative)

Embeds the OpenVisio web app. You get the full UI for free, but the iframe runs its own OIDC login and needs a SameSite=None session cookie — fragile and increasingly blocked by browsers. Prefer headless unless you specifically need the native UI.

import { OpenvisioBrowser } from '@openvisio/sdk-js/browser';

const browser = new OpenvisioBrowser({ openvisioUrl: 'https://openvisio.example.com' });
await browser.authenticate();    // SSO popup (silent if already logged in)
const iframe = browser.mountIframe(document.getElementById('openvisio-container')!, {
  slug: 'aaa-bbbb-ccc',
  accessToken,                   // from your backend (createToken)
});
browser.leave(iframe);           // destroy iframe FIRST -> no "excluded" banner
// ...then call your backend so it runs openvisio.leave(room_name, userId).

Error handling

Every server call throws OpenvisioError (with .status and .body) on a non-2xx response. The proxy's error detail is preserved in the message.

import { OpenvisioError } from '@openvisio/sdk-js';

try {
  await openvisio.interactions.unmuteMic(roomName, userId);
} catch (e) {
  if (e instanceof OpenvisioError && e.status === 404) {
    // participant joined with mic off, or isn't in the room — nothing to unmute
  }
}

Common codes: 404 — participant not in room / track not published (moderation and interactions are consistent here); 403 — guest blocked by your authz; 409 — your backend's "no active recording to stop"; 502/503 — OpenVisio backend or identity provider unreachable.


Reactions

interactions.reaction() broadcasts a reaction to everyone, but a server-sent reaction shows only the toast — the animated emoji on tiles needs a sender participant. In the headless pattern you have the LiveKit client, so publish the reaction yourself with room.localParticipant.publishData(...) (payload above) and the animation plays. The server-side call is still useful for reactions triggered by your backend.


Sample app

A runnable reference lives at vopenia-io/openvisio-sdk-js-sample — OIDC login (Authorization Code + PKCE), is_administrable admin gating, live mic/cam/hand/chat/recording feedback, plus a toggle to switch between the headless flow and the iframe flow (which loads OpenvisioBrowser from the CDN to demonstrate the published package).


Development

npm install
npm run build       # -> dist/ (ESM + CJS + .d.ts, one bundle per entry point)
npm test            # vitest — locks the request shapes against the proxy
npm run typecheck   # tsc --noEmit

Publishing

Releasing is a single manual step. Prepare the version, then run the workflow:

# 1. Bump the version + add a matching CHANGELOG.md entry, commit, merge to main.
#    (Only edit package.json's version — the tag is created by the workflow.)

Then open the Actions tab → ReleaseRun workflow (on main). The .github/workflows/release.yml workflow reads the version from package.json, refuses if that tag already exists or has no ## [version] CHANGELOG entry, runs typecheck/tests/build, publishes to npm, tags vX.Y.Z, and creates a GitHub Release from the CHANGELOG section.

Everything happens in that one run — there is no separate "push a tag to publish" step. (A tag pushed by CI's default token can't trigger another workflow, so the publisher runs directly on the manual dispatch instead.) The published tarball ships dist/, README.md, CHANGELOG.md and LICENSE (per files in package.json) — sources, tests, and examples stay out.

One-time setup (no secret): on npmjs.com the package uses Trusted Publishing (OIDC) — package Settings → Trusted Publisher → GitHub Actions, repo vopenia-io/openvisio-sdk-js, workflow release.yml. The workflow then authenticates automatically via its OIDC token; there is no NPM_TOKEN to store.

License

MIT