A framework-free, vanilla JavaScript reference for the HyperBabel API Platform. Drop-in code that shows how to call the public HTTP endpoints, hold a live channel subscription, and join a 1:1 video call — all using vanilla browser modules and a Vite dev server.
Authentication uses Customer Auth pattern B1 — Firebase Direct
Exchange. The browser signs in with Firebase, exchanges the resulting
ID token for a short-lived HyperBabel customer JWT, and uses that JWT
for every subsequent API call. The integrator's organization API key
(hb_live_… / hb_test_…) never ships in the browser bundle. The
HTTP client throws at startup if it ever sees one.
For the full architecture, see the Customer Auth section on the docs site. The per-org Firebase project allow-list is configured in the HyperBabel Console under Customer Auth.
If you are using React, React Native, Flutter, Swift, or Kotlin, the
same endpoints, request bodies, and response shapes apply — see the
sibling sample_demos/* projects.
| Feature | APIs Used |
|---|---|
| Sign in / Sign up | Firebase Auth → POST /customer/auth/firebase-exchange |
| Room list & creation | GET /unitedchat/rooms, POST /unitedchat/rooms |
| Chat (send / receive / edit / delete / typing / reactions / reply) | /unitedchat/rooms/:id/messages* (reactions included — room-scoped; replies use the top-level reply_to field) + Real-Time push |
| Message translation | POST /unitedchat/rooms/:roomId/messages/batch-translate |
| Image / file upload | POST /storage/presign → PUT signed URL → POST /storage/confirm |
| Read receipts | POST /unitedchat/rooms/:roomId/read |
| Members & moderation | GET /unitedchat/rooms/:id/members, ban / sub-admin / freeze / mute |
| Block list | GET /users/:id/blocks, POST /users/block, DELETE /users/block |
| Presence heartbeat | POST /presence/heartbeat, GET /presence?user_ids=… |
| 1:1 Video call | POST /unitedchat/rooms/:roomId/video-call, …/accept, …/reject, …/active, …/joined-rtc, …/heartbeat, …/renew-token, …/leave + HyperBabel Video |
| Incoming call | Real-Time event signal with type: "CALL_INVITE" on the user's private channel |
| Call events | video_call system messages on the room channel (metadata.event: call_started / call_accepted / call_ended / call_rejected / call_missed / call_busy) |
| In-call live captions | wss /stt-relay (Speech Translation — live STT + translation subtitles) |
| Live stream (host) — needs your backend | POST /stream/sessions, …/start, …/end + HyperBabel Video (broadcaster) |
| Live stream (viewer) — needs your backend | POST /stream/sessions/:id/viewer-token + HyperBabel Video (audience) |
| Push tokens | POST /push/register, GET /push/tokens |
| Usage stats | GET /auth/usage |
| Language detection — needs your backend | POST /translate/detect |
| Token issuance | POST /rtm/token, POST /rtm/rtc/token |
/stream/* (live streaming, host and viewer) and /translate/*
(text translation, language detection) accept only your organization
API key. This demo signs users in with a customer JWT and never holds
that key, so those calls are refused here (401 invalid_api_key_format) — the Streams screens show the resulting
"Invalid API key format" error, and the Settings screen explains it
instead of calling /translate/detect.
To ship them, route the call through your own server:
browser → your backend (holds hb_live_…) → HyperBabel. The paths and
payloads in src/api/stream.js and src/api/translate.js are accurate —
implement them on your server and expose a thin endpoint to the browser.
Chat-message translation does not need this: it uses the room-scoped
batch-translate endpoint, which accepts the customer JWT.
- Node.js 20+
- A free Firebase project (free tier is enough)
- A HyperBabel organization — sign up at https://console.hyperbabel.com
-
Sign up at the HyperBabel Console — https://console.hyperbabel.com. Once your organization exists, open Customer Auth → Add Firebase project.
-
Allow-list your Firebase project. In the console wizard:
- Paste your Firebase project ID (e.g.
your-app-prod). - Paste a Firebase ID token to prove ownership.
- Click Verify and add. This step tells HyperBabel "trust ID tokens from this Firebase project."
- Paste your Firebase project ID (e.g.
-
Enable sign-in methods in Firebase Console:
- Authentication → Sign-in method → enable Email/Password (and Anonymous if you want the kiosk-mode button on the login screen).
- Authentication → Settings → Authorized domains → ensure your dev
origin (
localhostis allow-listed by default) and any prod hostname are present. Without this, Firebase rejects sign-in withauth/unauthorized-domain.
-
Copy your Firebase Web SDK config from Firebase Console → Project Settings → Your apps → Web → "Config" snippet. You need
apiKey,authDomain,projectId,storageBucket,messagingSenderId,appId. -
Install, configure, and run:
cd sample_demos/javascript cp .env.example .env.local # → paste your Firebase Web SDK values into the VITE_FIREBASE_* slots npm install npm run dev
Open http://localhost:5175 in your browser. The login screen renders
a sign-in form when Firebase is configured, or a "Firebase config
missing" hint otherwise. Sign in (or create an account), and the demo
exchanges the Firebase ID token for a customer JWT, stores the pair in
localStorage, and routes you into the room list.
The customer JWT lives in localStorage, which is XSS-readable. This
is the inherent cost of any client-direct B1 flow. The risk is
bounded — customer JWTs are short-lived (1 h access, 30 d refresh) and
scoped to a single end-user; they cannot create new users or touch
billing. For higher assurance, host an httpOnly-cookie backend that
brokers the exchange (pattern B2 in the
Customer Auth docs).
| Variable | Required | Description |
|---|---|---|
VITE_HB_API_URL |
no | API base URL. Defaults to https://api.hyperbabel.com/api/v1. |
VITE_FIREBASE_API_KEY |
yes | Firebase Web SDK — apiKey |
VITE_FIREBASE_AUTH_DOMAIN |
yes | Firebase Web SDK — authDomain |
VITE_FIREBASE_PROJECT_ID |
yes | Firebase Web SDK — projectId |
VITE_FIREBASE_STORAGE_BUCKET |
yes | Firebase Web SDK — storageBucket |
VITE_FIREBASE_MESSAGING_SENDER_ID |
yes | Firebase Web SDK — messagingSenderId |
VITE_FIREBASE_APP_ID |
yes | Firebase Web SDK — appId |
There is no API-key env var — the demo only accepts customer JWTs
minted via Firebase Direct Exchange. Setting VITE_HB_API_KEY to an
hb_live_… / hb_test_… value makes the HTTP client throw at startup.
To send the demo's API calls somewhere else — for example a server you run locally in front of the HyperBabel API — point it there:
VITE_HB_API_URL=http://localhost:3000/api/v1HyperBabel APIs enforce Strict Origin Validation for org API keys. That validation does NOT apply to customer JWTs from Firebase Direct Exchange (the bearer is the per-end-user JWT, not your org key), so this demo works from any authorized Firebase domain without extra console configuration.
javascript/
├── index.html # entry document with header + <main>
├── package.json
├── vite.config.js
├── .env.example # environment variables template
└── src/
├── main.js # hash router & app shell
├── styles.css # demo styling
├── api/
│ ├── client.js # Customer JWT HTTP client (B1)
│ ├── firebaseAuth.js # Firebase → /customer/auth/firebase-exchange
│ ├── auth.js # /auth/usage
│ ├── chat.js # room-scoped emoji reactions
│ ├── unitedChat.js # rooms / messages / translation / moderation / video-call
│ ├── stream.js # live stream session lifecycle (needs your backend)
│ ├── storage.js # 3-step presign upload (envelope-aware)
│ ├── translate.js # AI Translation text / detect / languages (needs your backend)
│ ├── presence.js # online status heartbeat + bulk lookup
│ ├── push.js # token register / list / unregister
│ ├── users.js # global block list
│ └── rtm.js # token issuance for Real-Time + Video
├── realtime/
│ └── hyperbabelRealtime.js # Real-Time client (vendor SDK aliased)
├── video/
│ └── hyperbabelVideo.js # Video client (vendor SDK aliased)
├── incomingCall.js # global CALL_INVITE listener + Accept / Reject overlay
├── callEvents.js # call events on the room channel
└── pages/
├── login.js # Firebase Email/Password + Anonymous
├── signup.js # Firebase createUser → exchange
├── home.js # room list + create
├── chat.js # ChatScreen UX (typing / reactions / reply / translate / edit / delete / image / file / freeze / mute / members)
├── videoCall.js # 1:1 video call surface (joined-rtc / heartbeat / token renewal / leave)
├── streams.js # live stream discovery (needs your backend)
├── streamHost.js # host broadcasts as publisher (needs your backend)
├── streamViewer.js # viewer subscribes as audience (needs your backend)
├── blocks.js # global block list management
└── settings.js # API usage + push tokens + logout
- Auth. Copy
src/api/firebaseAuth.jsandsrc/api/client.js. The first owns Firebase sign-in / sign-up / exchange / sign-out; the second owns the customer JWT lifecycle (proactive refresh + 401 fallback + org-key guard). - HTTP services. The modules in
src/api/are purefetchcalls against the public HyperBabel API — copy whichever you need. - Real-Time push.
src/realtime/hyperbabelRealtime.jsshows how to exchange a token viaPOST /rtm/tokenand subscribe to a room channel. The underlying SDK is wrapped behind a thin facade so the vendor name never leaks into app code. - Video.
src/video/hyperbabelVideo.jsmirrors the same pattern for 1:1 / group video calls. Tokens come fromPOST /rtm/rtc/token; the SDK handles the media streams.src/pages/videoCall.jsshows the session upkeep a call needs:…/joined-rtconce joined,…/heartbeatevery minute, and…/renew-tokenbefore the one-hour RTC token expires. Every call action (accept,reject,end,leave, …) sends the call'ssession_id. - Call events. Call state changes arrive on the room's channel as
video_callsystem messages (src/callEvents.js). The call page closes oncall_ended; the caller of a 1:1 call stops on a decline, missed call or busy signal, and gives up when nobody joins within 45 s (restarted once someone accepts, so a late answer still connects). The incoming-call overlay closes when the caller hangs up and reports a missed call after 45 s of ringing.
MIT — see the project root LICENSE.
Disclaimer. This code is provided for demonstration purposes only. Add proper error handling, telemetry, and end-to-end testing before shipping.
Video and live streaming are metered by resolution tier, decided by the total resolution each participant receives. This demo keeps every call inside the HD budget (921,600 px per participant) and declares the matching tier on every session-creation call:
src/video/videoQuality.js— the presets and thequalityvalue are both defined here.- 1280 × 720 when a participant receives at most one remote stream (live-stream host, 1:1 call); 640 × 480 from three participants up, so a four-way call still totals 3 × 307,200 = 921,600 px.
qualityis sent as"hd". If you publish above these presets, change the preset and the declared tier together in that one file — the declared value is what your invoice is calculated from (Terms §5.1 / §5.2).- The roster is re-evaluated on every join and every leave, so a call that drops from four participants to two moves back up to 1280 × 720 and a participant who rejoins pulls everyone back down before publishing a frame.
Every session-creation call in this demo sends publish_resolution alongside
quality, and your app should too. The API accepts a request without it,
but leaving it out is how the most common billing surprise happens.
POST /api/v1/video/sessions
{
"call_type": "group",
"participants": [ ... ],
"quality": "hd",
"publish_resolution": { "width": 640, "height": 480 }
}
- What the value means. The resolution this session will actually publish
at this participant count — not your camera's maximum, and not a
constant. Build it with
publishResolutionFor(participantCount)insrc/video/videoQuality.js, which derives it from the same presets the encoder uses, so the number you send and the pixels you emit cannot drift apart. - What HyperBabel does with it. It multiplies the value by the number of
streams one participant receives — participants − 1 for a call, 1 for a
broadcast, since a viewer subscribes to the host only — and compares the
total against
quality. If the total lands in a higher tier, the creation response carries aquality_warningstring. The session is still created and nothing is blocked. - It never changes your bill. Billing follows
quality, always. This field exists so you can catch a wrongqualitybefore the invoice does. - Why it matters. The mistake it catches is not dishonesty, it is a unit
mismatch: 720p is genuinely HD in a 1:1 call and genuinely above HD in a
four-way one, because tiers are computed on the total each participant
receives. Declaring
"hd"while publishing 720p to three other people is an honest answer to the wrong question — and without this field nothing tells you so. - Read
quality_warningand act on it. Log it at minimum. If it appears, either lower the publishing resolution or declare the tier it names. Do not ignore it: your invoice is calculated fromquality, and the difference is recoverable under Terms §5.2.
See the root README for the full table and the reasoning.