Embed
embed.js puts the chat in an iframe on your page and talks to it. It is plain ES2020 with no dependencies and is under 8 KB gzipped.
Before you start
Section titled “Before you start”- An app id, and the page’s origin listed under Origins in the dashboard. See Tenants and apps.
- A token endpoint on your server that answers a client token for the logged-in user, or an empty body when nobody is logged in. See Sessions and tokens.
- If your pages send a Content Security Policy, allow
https://app.mondochat.ioinscript-srcandframe-src, and allow the inline snippet or move it into a file of your own. embed.js sets styles through the CSSOM, so it needs nostyle-src 'unsafe-inline'. - If your pages send a
Permissions-Policy, it must allowcameraandmicrophoneforhttps://app.mondochat.io, or calls have no camera.
-
Put the snippet on every page that shows the chat. Calls made before the script loads are queued.
<script>window.MondoChat = window.MondoChat || function () { (MondoChat.q = MondoChat.q || []).push(arguments) };MondoChat('init', { appId: 'APP_ID', getToken: async () => (await fetch('/mondochat-token')).text() || null });</script><script async src="https://app.mondochat.io/embed.js"></script> -
Open a conversation from your own button.
withis the other user’s id on your site:<button onclick="MondoChat('open', { with: 'model-42' })">Chat</button>A user must exist before others can open a conversation with them. They exist after their first session, or you create them from your server. See Users.
MondoChat('open')withoutwithopens the inbox. After the script loads,MondoChat.open({...})andMondoChat('open', {...})are the same. -
Listen for events, for example to draw your own unread badge:
MondoChat('on', 'unread', ({ count }) => { badge.textContent = count || '' }) -
Call
MondoChat('logout')when the user logs out of your site.
init options
Section titled “init options”Call MondoChat('init', options) once per page. A second call is ignored.
| Option | Type | Meaning |
|---|---|---|
appId |
string | Required. Your app id. |
getToken |
() => Promise<string or null> |
Required. Returns a client token for the logged-in user, or null when nobody is logged in. It is called on start and whenever the session must be renewed, and the frame does not send a second request while one is waiting for its answer. Return a fresh token each time: a token handed out before may already be spent. |
lang |
'it', 'en', 'es', 'fr' |
Interface language. Without it: the page’s <html lang>, then the user’s lang, then the browser’s languages, then i18n.default_lang. Only languages in i18n.languages count. |
mode |
'launcher' or 'inline' |
launcher (default) floats a bubble in a corner. inline fills container. |
container |
Element | Where the iframe goes. Default document.body. |
onPurchase |
(purchase) => Promise<{status, external_ref?}> |
Needed only with commerce.payment_mode: page_callback. See below. |
preload |
true |
Create the iframe when the browser is idle instead of at once. |
With getToken returning null, the chat signs in a guest if guests.enabled is on, and otherwise stays signed out.
Methods
Section titled “Methods”| Method | Arguments | Effect |
|---|---|---|
open |
{with?, vouch?} |
Shows the chat. with is an external id: it opens or starts the conversation with that user. Without it, it opens the inbox. |
close |
Hides the chat. | |
logout |
Drops the guest token and the chat session. | |
setLanguage |
lang |
Switches the interface language. |
refresh |
Refetches inbox, conversation and catalog, for example after a purchase made on your page. | |
on, off |
event, handler |
Subscribe and unsubscribe. |
Events
Section titled “Events”| Event | Data | When |
|---|---|---|
ready |
{} |
The chat connected to the page, once. |
open, close |
{} |
The chat opened or closed. A full-screen call counts as open. |
unread |
{count} |
The unread total changed. |
message |
{conversation_id, from: {external_id, name}, preview} |
A message arrived. |
call.incoming |
{from: {external_id, name}} |
Someone is calling. |
navigate |
{url, reason, preventDefault()} |
The chat wants to leave to url. See below. |
error |
{code} |
code: 'unauthorized' when the session ended and getToken gave no new token. |
The navigate event
Section titled “The navigate event”reason is one of signup, login, profile, services, checkout, topup, conversation or notify. The default is a top-level navigation to url. Call preventDefault() to handle it yourself.
profileandserviceswith nourlcarry the peer’sexternal_id, so your page resolves the page itself. They have nourlwhilelinks.profile_url_templateorlinks.services_url_templateis null.topupwith nourl(nocommerce.topup_url_templateand notopup_urlfrom the charge callout) carriespurchase_id,amount_minor,currencyand the seller’sexternal_id, so your site runs its own top-up.notifyhas nourl. A guest asked to be told when the peer replies, and the event carries their ownexternal_idand theconversation_id. Open your own page to collect and confirm an address, after checking who the viewer is by your session or by the stored guest token against/v1/.well-known/jwks.json. mondochat stores no end-user email.notifycomes from the settingguests.notify_prompt.
The settings overview lists every URL template and what happens while it is unset.
Vouch tokens
Section titled “Vouch tokens”A policy row with require_vouch: true lets a user start a conversation only with a vouch for that peer: a proof from your server that it sent this user to that peer, for example from the peer’s profile page. There are two ways:
- Put
peer: '<external id>'in the client token. It applies for the whole session. - Sign a vouch token and pass it to
open:MondoChat('open', { with: 'bob', vouch }).
A vouch token is HS256 with the server secret and header typ: vouch+jwt. Its claims are iss (the app id), peer, an optional sub (the user it is for; without it, whoever holds it, guests included), iat, and exp at most 600 seconds later.
function vouchToken(peer, sub) { const iat = Math.floor(Date.now() / 1000) const claims = { iss: APP_ID, peer, ...(sub && { sub }), iat, exp: iat + 300 } const body = `${b64url(JSON.stringify({ alg: 'HS256', typ: 'vouch+jwt' }))}.${b64url(JSON.stringify(claims))}` return `${body}.${createHmac('sha256', SECRET).update(body).digest('base64url')}`}b64url, APP_ID and SECRET are as in the Node server of the quickstart. A vouch token cannot be used as a client token.
Purchases paid by your page
Section titled “Purchases paid by your page”With commerce.payment_mode: page_callback, a purchase in the chat calls your onPurchase with the purchase ({id, item, price_minor, currency, ...}). Take the payment on your page, then resolve with {status: 'paid' | 'failed' | 'cancelled' | 'pending', external_ref?}. A rejected promise counts as failed.
The browser’s answer only updates what the buyer sees. The purchase is settled when your server confirms it with POST /v1/server/purchases/{id}/confirm:
curl -X POST https://app.mondochat.io/v1/server/purchases/$PURCHASE_ID/confirm \ -H "Authorization: Bearer $MONDOCHAT_SERVER_SECRET" -H "Mondochat-App-Id: $MONDOCHAT_APP_ID" \ -H 'Idempotency-Key: confirm-'"$PURCHASE_ID" -H 'content-type: application/json' \ -d '{"status": "paid", "external_ref": "order-1234"}'The other payment modes need nothing on the page: server_charge goes through the charge callout and checkout_url through a redirect. See Commerce.
Get the app
Section titled “Get the app”With pwa.enabled, the embed offers a seller a dismissible Get the app card at the top of the inbox, and a Get the app row in settings. Buyers and guests never see either. See Installed app.
What embed.js does to your page
Section titled “What embed.js does to your page”- Adds one iframe with
src="https://app.mondochat.io/frame/?app=<appId>",referrerpolicy="origin",allow="camera; microphone; autoplay; fullscreen; picture-in-picture",allowfullscreen, a translatedtitleand nosandbox. Withmode: 'inline'the address gets&mode=inline. - Sizes the iframe for the hidden, launcher, open and full-screen call states. The launcher is wider for a few seconds while a message toast shows beside the bubble. On a touch screen up to 768 px wide or 520 px tall an open chat fills the screen. Elsewhere it is a 400 by 620 panel above the bubble, which stays on the page and closes it.
- While the launcher shows, only the bubble, its badge and a toast take taps. The rest of the iframe passes them to your page.
- When the chat is closed and its own sound is locked (iPhone), plays the arrival chime on your page, primed by the visitor’s first tap or key press there.
- Stores one key,
localStorage['mondochat:<appId>:guest'], which holds the guest token. It sets no cookies. - Talks to the iframe only over a
MessageChannelset up after checking the iframe’s origin. See Security and the frame protocol.
Check it works
Section titled “Check it works”- A launcher bubble appears in the corner once
getTokenreturns a token. MondoChat('on', 'ready', () => console.log('ready'))logs once the chat connects.MondoChat('open', { with: '<another user id>' })opens that conversation.
When it fails
Section titled “When it fails”| Symptom | Cause |
|---|---|
Nothing appears and the console shows a frame-ancestors error |
The page’s origin is not listed under Origins for the app. |
The error event fires with unauthorized |
The session ended and getToken returned no new token. |
getToken returns a token that is refused |
A token with jti works once, and a token handed out before may already be spent. Sign a fresh one on every call. |
| Calls have no camera | Your Permissions-Policy does not allow camera and microphone for https://app.mondochat.io. |
| The script or the frame is blocked | Your Content Security Policy does not allow https://app.mondochat.io in script-src and frame-src. |
| Opening a chat fails with 404 | The other user has never signed in and was not created from your server. |
Limits
Section titled “Limits”initruns once per page, and the frame sends onegetTokenrequest at a time.onPurchaseis used only withcommerce.payment_mode: page_callback.- An app lists at most 20 origins, and together they take at most 1024 bytes.
- The only thing stored on your page is the guest token.