10-line integration story
Each of the four starters below — Next.js, Vite + React, iOS, or Android — is a self-contained snippet: paste it, run the app, and two clients can connect to the same room.
Every snippet here is written against the current SDK public API — the same
LevelChat,Room, and hook surfaces the packages export today.
The 10 lines (Next.js)
import { LevelChat } from '@levelchat/web';
export default function Page() {
const join = async () => {
const lc = new LevelChat({ logLevel: 'warn' });
const { token } = await fetch('/api/lc-token', { method: 'POST' }).then((r) => r.json());
const live = await lc.joinLive({ token, roomType: 'meeting' });
await live.publishCamera();
await live.publishMic();
};
return <button onClick={join}>Join</button>;
}That's it. Nine of those lines are not boilerplate — every one earns its keep:
import { LevelChat }— single named import, tree-shakes the rest.new LevelChat({ logLevel: 'warn' })— production default. Use'debug'while developing.fetch('/api/lc-token', { method: 'POST' })— your server mints a short-lived JWT and returns{ token }. 10-line server snippet below.lc.joinLive({ token, roomType: 'meeting' })— same call shape for every topology (1to1,meeting,live,webinar).live.publishCamera()— kicks offgetUserMediaand adds the track to the SFU.live.publishMic()— same, for audio.
That's the full developer surface area for the happy path. Reconnects, glare avoidance, codec negotiation, encryption keys — the SDK handles them.
The 10-line server (Node)
A standalone @levelchat/node package is on the roadmap; until it ships, fetch to the
public token-mint endpoint with your project API key:
export async function GET(req: Request) {
const userId = await yourAuth(req); // your existing auth
const roomId = new URL(req.url).searchParams.get('room') ?? 'demo-room';
const identity = userId ?? `user-${crypto.randomUUID().slice(0, 8)}`;
const res = await fetch(`${process.env.LEVELCHAT_API_URL}/v1/auth/tokens/room`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.LEVELCHAT_API_KEY}`, // lc_pk_xxx.yyy
},
body: JSON.stringify({
roomId,
userId: identity,
displayName: identity,
identity,
roomType: 'meeting', // 'meeting' | 'live' | 'webinar' | '1to1'
role: 'publisher',
caps: ['publish:camera', 'publish:mic', 'subscribe:all'],
ttlSeconds: 60,
}),
});
if (!res.ok) return new Response('token mint failed', { status: 500 });
return Response.json(await res.json()); // { token, expires_at }
}Three things to note:
- No external auth library. Call this after your own auth stack (Auth.js, Clerk, NextAuth, custom JWT, ...) has resolved who the user is — pass that identity into the body.
- TTL is 60 seconds. The token only authorises the WebSocket upgrade — once the SDK is connected, the SFU has its own session. Short TTLs blow up clean if a token leaks.
roleis'publisher'for participants who need to publish camera/mic. Other values:'viewer'(subscribe-only),'co-host'(publisher + moderate),'webinar-attendee'(subscribe-only with chat),'webinar-panelist'(publish + chat). Full table on/api.
Vite + React
Same Vite/React stack — install the React bindings on top of the vanilla SDK:
npm install @levelchat/web @levelchat/web-reactimport { useEffect, useState } from 'react';
import { LevelChatProvider, ParticipantGrid, useLocalParticipant } from '@levelchat/web-react';
export default function App() {
const [token, setToken] = useState<string>();
useEffect(() => {
fetch('/api/lc-token?room=r_demo', { method: 'POST' })
.then((r) => r.json())
.then((d) => setToken(d.token));
}, []);
if (!token) return <p>Connecting…</p>;
// `autoJoin` takes the same options as `client.joinRoom`. The provider
// joins on mount and tears the room down on unmount.
return (
<LevelChatProvider autoJoin={{ token }}>
<ParticipantGrid />
<Controls />
</LevelChatProvider>
);
}
function Controls() {
// `useLocalParticipant` exposes the room's publish helpers.
const { publishCamera, publishMic } = useLocalParticipant();
return (
<>
<button onClick={() => publishCamera()}>Share camera</button>
<button onClick={() => publishMic()}>Share mic</button>
</>
);
}You fetch the token from your server's /api/lc-token route (from §"The 10-line server"
above) and hand it to LevelChatProvider via autoJoin — it opens the WS + ICE behind a
single context. Under the hood it's the same vanilla @levelchat/web SDK — same call shape,
same wire format, no second engine to debug.
iOS (Swift)
import LevelChat
import SwiftUI
struct ContentView: View {
var body: some View {
Button("Join") {
Task {
let token = try await TokenAPI.fetch()
let client = try await LevelChat(config: LevelChatConfig(logLevel: .info))
let room = try await client.joinRoom(JoinRoomOptions(token: token))
try await room.publishCamera()
try await room.publishMic()
}
}
}
}room.events is an AsyncStream<RoomEvent> — iterate it with for await in a Task and
drive your SwiftUI state from the typed events (participantJoined, connectionState, …).
Android (Kotlin)
import ai.levelchat.sdk.LevelChat
import ai.levelchat.sdk.JoinRoomOptions
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// `LevelChat` is built via the `create` factory, not `new`.
val lc = LevelChat.create(context = applicationContext)
setContent {
Button(onClick = {
lifecycleScope.launch {
val token = TokenAPI.fetch()
val room = lc.joinRoom(JoinRoomOptions(token = token))
room.publishCamera()
room.publishMic()
}
}) { Text("Join") }
}
}
}Declare CAMERA and RECORD_AUDIO in AndroidManifest.xml and request them at runtime
(ActivityResultContracts.RequestMultiplePermissions) before you call publishCamera /
publishMic — Android does not grant them implicitly.
What the SDK does NOT need from you
These are the things you do not have to write code for:
- STUN / TURN configuration. Default STUN ships built-in; TURN credentials come down with the JWT when present.
- ICE restart on network change. Built into the perfect-negotiation pipeline.
- Glare avoidance. Polite/impolite roles + collision detection — exact pattern from W3C webrtc-pc spec example.
- Bandwidth probing. We negotiate three simulcast layers (low / mid / high) automatically; the SFU drops the right layer per consumer.
- Reconnect with backoff. Exponential + jitter, capped at 30s.
If you find yourself writing code for any of the above, stop and email [email protected] — that's a sign the SDK has a gap, not a sign you need extra glue.
Staying in sync
These snippets target the SDK public API as it ships today — LevelChat, Room, and the
@levelchat/web-react hooks. They are not auto-generated or run by CI; they are written by
hand against the packages' public exports and reviewed when the SDK surface changes. If a
snippet ever drifts from the package it targets,
email [email protected].
What's next
/sdk/web— full Web SDK reference./guides/rooms— the four canonical topologies (1to1,meeting,live,webinar) and when to use each./guides/live-streaming— live-broadcast specifics (HLS fan-out, viewer tokens, recording)./guides/recordings— turn on recording and download fMP4 + LL-HLS.