10-line integration story
Promise. Take any of the four starters below — Next.js, Vite + React, iOS, or Android — and you can paste a 10-line snippet, run the app, and see two browser tabs talking to each other in under five minutes.
Promise behind the promise. Every snippet on this page is run end-to-end in CI before each release. If a snippet stops working, that release does not ship.
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').then((r) => r.text());
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')— your server mints a short-lived JWT. 10-line server snippet below.lc.joinLive({ token, roomType: 'meeting' })— same call shape for every topology (1:1, meeting, broadcast, 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 res = await fetch(`${process.env.LEVELCHAT_API_URL}/v1/rooms/${roomId}/tokens`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.LEVELCHAT_API_KEY}`, // lc_pk_xxx.yyy
},
body: JSON.stringify({
identity: userId ?? `user-${crypto.randomUUID().slice(0, 8)}`,
role: 'publisher',
capabilities: ['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, url }
}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 { LevelChatProvider, ParticipantGrid, useLocalParticipant } from '@levelchat/web-react';
export default function App() {
return (
<LevelChatProvider tokenEndpoint="/api/lc-token" room="r_demo" roomType="meeting">
<ParticipantGrid />
<Controls />
</LevelChatProvider>
);
}
function Controls() {
const { toggleCamera, toggleMic, camOn, micOn } = useLocalParticipant();
return (
<>
<button onClick={toggleCamera}>{camOn ? 'Camera off' : 'Camera on'}</button>
<button onClick={toggleMic}>{micOn ? 'Mic off' : 'Mic on'}</button>
</>
);
}The LevelChatProvider calls your tokenEndpoint (your server's /api/lc-token route from
§"The 10-line server" above), receives { token, url }, then 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 {
@StateObject private var room = LiveRoom()
var body: some View {
Button("Join") {
Task {
let token = try await TokenAPI.fetch()
try await room.join(token: token, roomType: .meeting)
try await room.publishCamera()
try await room.publishMic()
}
}
}
}LiveRoom is an ObservableObject; bind its state to a SwiftUI overlay for the full status indicator, no extra wiring required.
Android (Kotlin)
import io.levelchat.LevelChat
import io.levelchat.RoomType
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val lc = LevelChat(this)
setContent {
Button(onClick = {
lifecycleScope.launch {
val token = TokenAPI.fetch()
val live = lc.joinLive(token = token, roomType = RoomType.Meeting)
live.publishCamera()
live.publishMic()
}
}) { Text("Join") }
}
}
}The SDK auto-acquires RECORD_AUDIO and CAMERA permissions when joinLive is called — declare them in AndroidManifest.xml and you're done.
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 open a discussion on GitHub — that's a sign the SDK has a gap, not a sign you need extra glue.
CI guarantee
The four <10-line snippet, copy-paste, hit run> flows above are exercised end-to-end on every PR by tests/e2e/integration-stories/*.spec.ts. The matrix runs:
[next.js, vite-react, ios-simulator, android-emulator]
× [chromium, webkit, firefox]
× [meeting, 1:1]That's twenty-four scenario combinations per release. When any of them fails, the release is blocked, and the matching guide page on this site is updated to reflect what changed before the next attempt. No silent breakages.
What's next
/sdk/web— full Web SDK reference./guides/rooms— the four topologies (1:1, meeting, broadcast, webinar) and when to use each./guides/live-streaming— broadcast specifics (HLS fanout, viewer tokens, recording)./guides/recordings— turn on recording and download fMP4 + LL-HLS.