Browser-side contract
Your page and SuperPreneur exchange three messages. Everything is JSON. SuperPreneur only answers while the page shown is served from your registered origin, so a message sent from any other site is ignored.
| Direction | Message | When |
|---|---|---|
| Page to SuperPreneur | {"type": "ready", "nonce": "<16 to 128 characters: A-Z a-z 0-9 _ ->"} |
As soon as the page has fetched a nonce from your server |
| SuperPreneur to page | {"type": "launch", "token": "<signed token>"} |
The user is signed in and has approved your app |
| SuperPreneur to page | {"type": "error", "error": "<code>"} |
SuperPreneur refused (see below) |
How messages travel#
| Where your app runs | Your page sends with | SuperPreneur replies by |
|---|---|---|
| Mobile app (webview) | SuperappBridge.postMessage(JSON.stringify(msg)) |
calling window.__superappReceive(jsonString) on your page |
| Web shell (iframe) | window.parent.postMessage(msg, SHELL_ORIGIN) with an explicit origin, never * |
postMessage to your origin; your page accepts it only when event.source is its parent and event.origin equals SHELL_ORIGIN |
The mobile app injects SuperappBridge only into pages on your registered origin, and refuses to navigate anywhere else.
Error codes in an error message#
| Code | Meaning |
|---|---|
consent_required |
The user has not approved your app (or revoked it) |
unknown_app |
The app is not active, or the client id is wrong |
launch_failed |
SuperPreneur could not reach its backend |
Reference implementation#
There is no library to install: the whole browser side is the plain JavaScript below. It is the page in partner-examples/public/index.html, tested in a browser and in the phone app.
const NATIVE = typeof window.SuperappBridge !== 'undefined'; // true inside the mobile app
let shellOrigin = null; // the web shell's origin (only needed when framed by the web shell)
let session = null; // your own session, kept in memory only
let waiting = null; // set while we wait for SuperPreneur's answer
// Messages from SuperPreneur: the mobile app calls this function ...
window.__superappReceive = (json) => { try { waiting && waiting(JSON.parse(json)); } catch (_) {} };
// ... and the web shell posts a message from its own origin.
window.addEventListener('message', (e) => {
if (!NATIVE && waiting && e.source === window.parent && e.origin === shellOrigin) waiting(e.data);
});
function tellSuperapp(message) {
if (NATIVE) window.SuperappBridge.postMessage(JSON.stringify(message));
else window.parent.postMessage(message, shellOrigin); // explicit origin, never '*'
}
function waitForLaunch(ms = 10000) {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => { waiting = null; reject(new Error('SuperPreneur did not answer.')); }, ms);
waiting = (m) => {
if (m && m.type === 'launch') { clearTimeout(timer); waiting = null; resolve(m.token); }
else if (m && m.type === 'error') { clearTimeout(timer); waiting = null; reject(new Error('Launch refused: ' + m.error)); }
};
});
}
async function signIn() {
if (!NATIVE && window.parent === window) throw new Error('Open this app from SuperPreneur.');
if (!NATIVE && !shellOrigin) shellOrigin = (await (await fetch('/api/config')).json()).shell_origin;
const { nonce } = await (await fetch('/api/nonce', { method: 'POST' })).json(); // 1. ask your server
const launch = waitForLaunch(); // start listening BEFORE announcing, so the answer cannot be missed
tellSuperapp({ type: 'ready', nonce }); // 2. tell SuperPreneur you are ready
const token = await launch; // 3. it answers with a one-time token
const res = await fetch('/api/session', { // 4. your server verifies it and starts your session
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token, nonce }),
});
if (!res.ok) throw new Error('Sign-in was rejected (' + res.status + ').');
session = (await res.json()).session;
}
// Call your own API. If the session has expired, sign in again quietly and retry once.
async function api(path) {
const call = () => fetch(path, { headers: { Authorization: 'Bearer ' + session } });
let res = session ? await call() : { status: 401 };
if (res.status === 401) { await signIn(); res = await call(); }
return res;
}
Points to copy exactly#
- Start listening for the answer before you send
ready, or a fast reply can be missed. - Keep the session in memory only. Do not put the token or session in cookies, local storage or the URL; they are unreliable inside embedded pages, especially on iOS.
- If there is no
SuperappBridgeand the page is not framed, the app was opened directly: show "Open this app from SuperPreneur" and stop. - Set your own page background and text colour. The app shows a white page by default, but a page that sets no colours can still end up unreadable.