Ideapreneur Nepal logoSuperPreneur for Developers v1.0

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#