Quickstart
This is exactly the guide the starter kit gives your AI assistant (AGENTS.md). Hand it to Claude, Cursor or Copilot and ask for a game.
Workflow
npx @livebridge/dev init my-gamecreates a folder from the starter game (id: 3-40 chars of a-z, 0-9 and-, immutable later).- Edit
game.js,index.html,style.css,manifest.json. npx @livebridge/dev serveruns it athttp://localhost:5173/index.html?t=lbg_dev. Also open http://localhost:5173/__panel: a simulator panel to fire gifts, chat, likes, follows, shares, joins, change the viewer name and avatar, and switch name/avatar sending on and off.npx @livebridge/dev checkruns the server's own static checks locally. It must be clean.- Once: create a token at https://livebridge.vn/app/dev/cli then
npx @livebridge/dev login. After thatnpx @livebridge/dev upload(add--test-onlyto check without releasing; add--createif the game does not exist on the web yet). - Every upload must raise `version` in
manifest.json(for example 1.0.0 to 1.0.1).
AGENTS.md, CLAUDE.md, README.md, hidden files (starting with .) and node_modules are not part of the upload.
The LiveBridge SDK
Load it with exactly this line (before the game script). Do not copy or bundle the SDK: serve swaps this URL for a local copy, and in production it is LiveBridge's own.
<script src="https://livebridge.vn/sdk/livebridge.js"></script>
<script>
const game = new LiveBridgeGame({
onAction(actionId, event, rule) { /* a gift the streamer mapped to one of your actions */ },
onGift(event) { /* EVERY gift, mapped or not */ },
onChat(comment, user, event) {},
onFollow(user, event) {},
onShare(user, event) {},
onMember(user, event) {}, // someone joined the room
onLike(user, event) {}, // event.like.count = likes in this batch
});
</script>All handlers (all optional):
onReady(config, actionMap): the SDK has read the manifest and the gift mapping.configis your manifest.json. `actionMap` can be `null` if the first read failed (network error, 429): the mapping then arrives later throughonConfig. Never assumeactionMapis set.onConfig(actionMap): the streamer just saved the gift mapping (no page reload), or the mapping arrived late afteronReady.onAction(actionId, event, rule): a gift matched a rule.rulehasactionId,amount(default 1), andgiftNameorminDiamonds/maxDiamonds.onGift(event): every gift. Always called, also whenonActionis.onChat(comment, user, event),onFollow(user, event),onShare(user, event),onMember(user, event),onLike(user, event).onEvent(event): every raw event (gifts, chat, ...).onConnect(),onDisconnect(): socket opened / closed. The SDK reconnects after a few seconds unless the link was revoked.onTikTokStatus("connected" | "connecting" | "ended" | "disconnected"): state of the streamer's TikTok room. The socket can be open while the streamer has not pressed connect for TikTok.apiOrigin(optional): only needed if you self-host the SDK. Do not.- Helpers:
LiveBridgeGame.resolveGiftAction(actionMap, event),LiveBridgeGame.getDiamonds(event)(=diamondCount x repeatCount),LiveBridgeGame.getUser(event);game.destroy()stops it.
Event shape (after the privacy filter; optional fields may be missing, check before using):
{ type: "gift", seq, ts, channel,
user: { id, uniqueId, nickname, avatar, roles?: { moderator, subscriber, follower, member } },
gift: { id, name, diamondCount, repeatCount, icon? } } // chat: chat: { comment }
// like: like: { count, total? }gift.icon is always "" for developer games (the CSP blocks gift images from the TikTok CDN and LiveBridge has no gift-image proxy yet): draw your own gift icons if you need them.
Gift mapping rules: exact gift name > diamond range (diamondCount x repeatCount within minDiamonds..maxDiamonds) > the rule whose actionId is "default". The streamer can change the mapping at any time, so always handle onGift for unmapped gifts too.
Privacy (the streamer chooses per game). Names and avatars are sent by default. When the streamer turns names off: user.id becomes anon-<16 hex>, user.uniqueId becomes viewer-<4 hex>, user.nickname becomes Viewer #<4 hex>; each viewer always gets the same code within one room and game. user.avatar is an image URL through the LiveBridge proxy (https://livebridge.vn/api/avatar?u=<uniqueId>&s=96, allowed by the CSP; the proxy answers a default image when it has none), never a TikTok CDN URL. When avatars are off, and also when names are off (the proxy URL carries the real name): user.avatar is "". The game must look right in both cases (for example an initial letter instead of an avatar).
Token mode. The game only has ?t=. Without ?t= the SDK runs in demo mode (no connection, no error). A revoked link (the streamer pressed "Disconnect this game", changed TikTok account, an admin took the game down) closes the socket with code 4003: the SDK stops for good and calls onDisconnect; show "Link expired, reopen the game from LiveBridge". window.__LIVEBRIDGE__.status is idle, open, closed or revoked.
Create `new LiveBridgeGame({...})` as soon as the page loads, not after a click. The automatic checker opens the game in a real browser and waits for status open.
manifest.json
JSON Schema: https://livebridge.vn/schemas/game-manifest.json. Rules:
id: the id of the game you created,^[a-z0-9-]{3,40}$, immutable; no reserved word (admin,api,app,www,sdk,dev,developers,games,static,cdn,mail,livebridge,test) or existing game id.version:x.y.z, greater than the live one.name,summary,description: each has bothvianden, non-empty, no blocked words.platform:"web".connection:{ "needsApp": false, "sendUsername": true, "sendAvatar": true }(needsAppis alwaysfalse).entry: path of the main HTML file from the folder root (case-sensitive). Uploaded games useentry, noturl.cover: a 16:9 cover image (for example 1280x720) that exists in the folder.screenshots: at most 8 images.tags: at most 5.connectOrigins: where the game may call over the network, ashttps://hostorwss://host(lower case, no path, no*). Default[].actions: what the game can do, each{ id, name, name_vi, description, description_vi }.defaultGiftMapping: the suggested mapping, each row has eithergiftNameorminDiamonds(optionally withmaxDiamonds), plus anactionIdthat exists inactions.
Forbidden
- No network access outside `connectOrigins` (fetch, XHR, WebSocket, images, fonts beyond what the CSP section allows). If you need your own API, declare its origin in
connectOrigins. - Never store or send viewer data anywhere else: names, avatars, ids and comments are for display inside the game only; do not write them to localStorage, cookies, IndexedDB or your own server, and do not put them in URLs.
- Never ask for, read or use `?key=` or the streamer's connection code (room key). The game only receives
?t=. - Do not assume a keyboard or mouse: the game is used while the streamer is live, and the players are viewers acting through gifts and chat.
- No external analytics (Google Analytics, Sentry, Mixpanel, ...), no iframes or ad scripts.
- Viewer data is untrusted: show it with
textContent, neverinnerHTML,evalordocument.write. - No illegal, sexual, extreme-violence, hateful or real-money gambling content. LiveBridge may take a game down at any time (see the developer terms).
- Use relative paths (
game.js,./img/a.png), never starting with/: the game runs underhttps://<game-id>.<sandbox domain>/v/<version>/.
CSP (the sandbox content security policy)
Every game file is served with this CSP; serve applies the same one so you see problems early:
default-src 'self'.- Scripts: the game's own files, LiveBridge's
/sdk/, inline scripts, WebAssembly ('wasm-unsafe-eval'), and the CDNshttps://cdnjs.cloudflare.com,https://cdn.jsdelivr.net,https://unpkg.com. - Network calls (
fetch, XHR, WebSocket): the game itself, the LiveBridge API, andconnectOrigins. - Images, audio, video, fonts: the game's files,
data:,blob:, the LiveBridge API, the three CDNs andconnectOrigins(https). Google Fonts is allowed (CSS fromfonts.googleapis.com, fonts fromfonts.gstatic.com). - Web Workers: the game's files or
blob:. No<object>,<base>,<form action>. - The game can be embedded in OBS and the dashboard (no
frame-ancestors), but cannot embed outside pages.
How to test
npx @livebridge/dev serve [folder] --port 5173. It simulates LiveBridge's token endpoints (/api/game-token/actions|status|manifest,/ws?t=lbg_dev) with the fake tokenlbg_dev. The gift mapping comes fromdefaultGiftMappingin manifest.json (a real streamer may change it).- Panel
http://localhost:5173/__panel: gift buttons (a sample of TikTok gifts, with repeat count), chat, like x N, follow, share, join, "random viewer" (name + avatar), send-name / send-avatar switches (see what the game gets when the streamer turns them off), and a "resend config" button (exercisesonConfig). - After each change reload the game page, then run
npx @livebridge/dev check. - On upload LiveBridge also opens the game in a real browser against a fake API: the page must finish loading within 20 seconds, throw no uncaught JavaScript error, and the SDK must connect.
Pre-upload checklist (check codes and fixes)
check and upload print issues as [code] (field) with a "Fix" line. The usual fixes:
zip_invalid: re-create the archive with a normal zip (no password, no rar/7z). The CLI zips for you so this is rare.zip_too_big: more than 50 MB unpacked. Compress images/audio, delete unused files, or load big assets from a CDN.zip_too_many_files: more than 2,000 files. Bundle JS/CSS, combine images into sprite sheets.zip_path_escape: a path has..,\or starts with/. Rename the file.zip_symlink: a symbolic link. Replace it with the real file.zip_duplicate: two files differ only by letter case. Rename one.manifest_missing: nomanifest.jsonat the folder root.manifest_invalid: one field is wrong. Read the field name in the issue and fix it against the JSON Schema.version_not_newer:versionis not greater than the live one. Raise it.version_exists: this version number was already uploaded. Raiseversion.entry_missing:entrydoes not point to a real file. Fix the path (case-sensitive).cover_missing: the cover (or ascreenshotsimage) is missing. Add the file or fix the path.file_type_forbidden: only html htm js mjs css json png jpg jpeg gif webp avif svg ico mp3 ogg wav m4a mp4 webm woff woff2 ttf otf wasm txt map are allowed. Delete the others.words_blocked: the name/summary/description has an unsuitable word. Reword it.url_not_https,url_unreachable: forexternal_webgames (uploaded on the web, not through the CLI).smoke_timeout: the page did not respond within 20 seconds. Look for an infinite loop or heavy work at load.smoke_js_error: a JavaScript error during the test run. Open?t=lbg_dev, read the Console, fix it.smoke_sdk_not_connected: the SDK did not connect. Loadhttps://livebridge.vn/sdk/livebridge.jsand callnew LiveBridgeGame({...})on page load.smoke_load_failed: the entry could not be opened. Checkentryand that the file exists.smoke_infra: a problem on the checker's side. Upload the same version again in a few minutes; no need to change the version.sdk_missing(CLI only, a warning): no.htmlfile loads/sdk/livebridge.js.
Limits: at most 10 games per developer, 20 uploads per day, the files of the 5 newest versions are kept.
How to read a check failure
Every issue has a code, a field (if any), a description and a "Fix" line in Vietnamese and English. Work in this order: (1) fix the first issue exactly as its Fix line says; (2) run check again; (3) repeat. If the developer pastes you text that starts with LiveBridge upload check failed for <game> v<version>: (the Sao chép lỗi cho AI / copy-for-AI button on the web, or the last block of check), that is the issue list: fix each item.