osamlive
v1.0.1
Published
Official SDK for OsamLive — video/audio calling and live streaming for web, Next.js, Node.js, and React Native.
Maintainers
Readme
OsamLive
Video calling, group calls, and live streaming (with OBS Studio / RTMP support) for your own app — the same model as ZegoCloud or Agora, built on the open-source LiveKit media engine. This package is the SDK: it mints join tokens and manages rooms through your OsamLive project, and gives you a ready-made React layer for the actual call UI.
- Works everywhere on the server: Node.js, Next.js (API routes, Server Actions,
Route Handlers), Express, serverless functions, even Edge runtimes — it's built on
nothing but the global
fetch. - Works in the browser: a React hook + component for Next.js/React/Vite apps.
- Works in React Native: the server SDK runs fine inside a React Native app too
(again, just
fetch), and pairs with LiveKit's own official React Native SDK for the actual call screen — see the React Native section.
Compatibility
- React: 16.8 and up — 16, 17, 18, and 19 all work (
osamlive/reactonly uses hooks and JSX that have existed since 16.8; nothing 18/19-only). The published types import plainReactElement/CSSPropertiesfromreactrather than the newerReact.JSXnamespace, so they resolve cleanly under@types/reactfor any of those versions too. - TypeScript: no minimum version pinned — the shipped
.d.tsfiles avoid anything recent enough to trip up older compilers. Using this from plain JavaScript (no TypeScript at all) works fine too; the types are there if you want them, not required. - livekit-client: 1.x and 2.x. The server SDK (
osamlive) doesn't depend on it at all — onlyosamlive/reactdoes, and only on the stable core API (Room,Participant,Track,RoomEvent) that hasn't changed across that range. - Node.js: 18+ (needs global
fetch; polyfill it yourself on 16 if you're stuck there).
Install
npm install osamliveIf you're building the call UI in a browser (React/Next.js), also install LiveKit's client SDK, which this package's React helpers wrap:
npm install livekit-clientThe two halves of this SDK
| | Where it runs | What it needs | Import |
|---|---|---|---|
| Server SDK | Your backend only | Your project's App Secret | import OsamLive from "osamlive" |
| React helpers | Browser / client component | A { token, wsUrl } pair from your backend | import { useOsamLiveRoom } from "osamlive/react" |
Never put your App Secret in browser or mobile app code. The flow is always:
Your client (browser/app) → your backend → osamlive server SDK → OsamLive
▲ │
└──────────────────── { token, wsUrl } ────────────────────────────┘Your backend mints a fresh, short-lived token per real user right before they join a call. Never share one token across users, and never mint tokens ahead of time and store them.
Getting your credentials
- Log in to your OsamLive dashboard as a developer.
- Create a project. You'll be shown an App Key exactly once —
<appId>.<secret>. Save it somewhere safe (an environment variable, a secrets manager).
# .env (server-side only — never NEXT_PUBLIC_/EXPO_PUBLIC_/etc.)
OSAMLIVE_APP_KEY=proj_xxxxxxxxxxxxxxxx.yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
OSAMLIVE_BASE_URL=https://live.yourdomain.comOSAMLIVE_BASE_URL is the domain of your OsamLive deployment (self-hosted or the
hosted service you were given). If you're self-hosting, this is the domain you set up
for the dashboard/API — see your OsamLive service's own deployment docs.
Quick start — Node.js
import { OsamLive } from "osamlive";
const client = new OsamLive({
appKey: process.env.OSAMLIVE_APP_KEY,
baseUrl: process.env.OSAMLIVE_BASE_URL,
});
const { token, wsUrl } = await client.createToken({
roomName: "team-standup",
identity: currentUser.id,
name: currentUser.name,
});
// Send { token, wsUrl } to whichever client (browser/app) is about to join.Next.js
API route / Route Handler (app/api/call-token/route.ts) — this is where the App
Key actually lives:
import { NextResponse } from "next/server";
import { OsamLive } from "osamlive";
import { getCurrentUser } from "@/lib/auth"; // however you already do this
const client = new OsamLive({
appKey: process.env.OSAMLIVE_APP_KEY!,
baseUrl: process.env.OSAMLIVE_BASE_URL!,
});
export async function POST(req: Request) {
const user = await getCurrentUser(req);
if (!user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
const { roomName } = await req.json();
const { token, wsUrl } = await client.createToken({
roomName,
identity: user.id,
name: user.name,
});
return NextResponse.json({ token, wsUrl });
}Client component — fetches a token from your own route above, then joins:
"use client";
import { useEffect, useState } from "react";
import { useOsamLiveRoom, ParticipantView } from "osamlive/react";
export default function CallRoom({ roomName }: { roomName: string }) {
const { connect, disconnect, isConnected, participants, toggleCamera, toggleMicrophone } =
useOsamLiveRoom();
const [error, setError] = useState<string | null>(null);
useEffect(() => {
(async () => {
const res = await fetch("/api/call-token", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ roomName }),
});
const { token, wsUrl } = await res.json();
await connect({ token, wsUrl }).catch((err) => setError(err.message));
})();
return () => disconnect();
}, [roomName]);
if (error) return <p>Could not join call: {error}</p>;
return (
<div>
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(200px, 1fr))", gap: 12 }}>
{participants.map((p) => (
<ParticipantView
key={p.identity}
participant={p}
mirror={p.isLocal}
style={{ aspectRatio: "16 / 9", borderRadius: 12, overflow: "hidden", background: "#111" }}
/>
))}
</div>
{isConnected && (
<div style={{ marginTop: 12, display: "flex", gap: 8 }}>
<button onClick={toggleCamera}>Toggle camera</button>
<button onClick={toggleMicrophone}>Toggle mic</button>
<button onClick={disconnect}>Leave call</button>
</div>
)}
</div>
);
}Plain React (Vite, CRA, etc.)
Same as the Next.js client component above — osamlive/react doesn't depend on
Next.js at all. Just point your fetch("/api/call-token") call at wherever your backend
(any Node server, not necessarily Next.js) exposes the token-minting endpoint.
React Native
The server SDK (osamlive, not osamlive/react) is plain fetch-based JavaScript,
so it also runs inside a React Native app's JS engine without any changes — but exactly
as on web, it should still only ever run on your backend, not inside the app bundle
(a compiled mobile app is not a secure place for a secret either — it can be
decompiled). Have your backend expose the same kind of token endpoint shown in the
Next.js example above, and call it from the app with fetch.
For the actual call screen, pair the token your backend gives you with LiveKit's own
official React Native SDK, @livekit/react-native
(it's the native counterpart to the livekit-client the web helpers in this package
wrap — OsamLive is a LiveKit-based service, so any LiveKit-compatible client SDK works
against it):
npm install @livekit/react-native @livekit/react-native-webrtc livekit-client
npx pod-install # iOSimport { useState, useEffect } from "react";
import { LiveKitRoom, useTracks, VideoTrack } from "@livekit/react-native";
import { Track } from "livekit-client";
import { View, Button } from "react-native";
export function CallScreen({ roomName }: { roomName: string }) {
const [creds, setCreds] = useState<{ token: string; wsUrl: string } | null>(null);
useEffect(() => {
fetch("https://your-backend.com/api/call-token", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ roomName }),
})
.then((res) => res.json())
.then(setCreds);
}, [roomName]);
if (!creds) return null;
return (
<LiveKitRoom serverUrl={creds.wsUrl} token={creds.token} connect audio video>
<ParticipantGrid />
</LiveKitRoom>
);
}
function ParticipantGrid() {
const tracks = useTracks([Track.Source.Camera]);
return (
<View style={{ flex: 1 }}>
{tracks.map((t) => (
<VideoTrack key={t.publication.trackSid} trackRef={t} style={{ flex: 1 }} />
))}
</View>
);
}This React Native example follows
@livekit/react-native's own documented API — check that package's docs for the exact hooks/props on the version you install, since native SDKs move faster than this README. The one thing specific to OsamLive here is the token: however you fetch{ token, wsUrl }from your backend is the only OsamLive-specific part; everything else is standard LiveKit React Native usage.
OBS Studio / RTMP streaming
Instead of a browser/app publishing camera+mic, let an external encoder stream into the room:
const ingress = await client.createIngress({
roomName: "team-standup",
identity: "host",
name: "Host",
});
console.log(ingress.serverUrl); // paste into OBS → Settings → Stream → Server
console.log(ingress.streamKey); // paste into OBS → Settings → Stream → Stream Key
// When the broadcast ends:
await client.stopIngress(ingress.ingressId);Viewers who already joined via createToken see the OBS feed exactly like any other
publishing participant — no extra code needed on their side.
API reference
new OsamLive(options)
| Option | Type | Required | Description |
|---|---|---|---|
| appKey | string | one of appKey or appId+secret | <appId>.<secret>, shown once at project creation |
| appId | string | | Alternative to appKey |
| secret | string | | Alternative to appKey |
| baseUrl | string | recommended | Your OsamLive deployment's URL |
client.createToken({ roomName, identity, name?, canPublish?, canSubscribe? })
→ Promise<{ token: string; wsUrl: string; iceServers?: IceServer[] }>. Mint a join
token for one participant. canPublish/canSubscribe default to true; set
canPublish: false for a view-only participant. iceServers is present only if your
OsamLive deployment has TURN configured — pass it straight through to connect() (see
below) for reliable connectivity behind restrictive NATs/firewalls.
client.createRoom({ roomName })
→ Promise<{ roomName: string }>. Optional — a room is created automatically the first
time someone joins with a token for it.
client.endRoom(roomName)
→ Promise<void>. Ends the room immediately, disconnecting everyone in it.
client.createIngress({ roomName, identity, name? })
→ Promise<{ ingressId: string; serverUrl: string; streamKey: string }>. See
OBS Studio / RTMP streaming above.
client.stopIngress(ingressId)
→ Promise<void>.
Errors
Every method throws OsamLiveError ({ name: "OsamLiveError", message, status }) on
failure — wrap calls in try/catch and read .message for a user-facing reason and
.status for the HTTP status code.
osamlive/react
useOsamLiveRoom()→{ room, isConnected, isConnecting, error, participants, connect, disconnect, toggleCamera, toggleMicrophone, isCameraEnabled, isMicrophoneEnabled }.connect({ token, wsUrl, publish?, iceServers? })— passiceServersstraight through fromcreateToken's result for reliable connectivity if your deployment has TURN configured.<ParticipantView participant={p} mirror? className? style? />— renders one participant's video (and, for remote participants, audio). Setmirroronly on the local participant's own tile.
License
MIT
