Skip to main content
A Salesive app can ship an embedded UI that opens inside the merchant’s dashboard — like a panel or full-screen sheet — alongside its backend API access. This page documents how that works: the iframe environment, the background pre-load system, lifecycle events, and the runtime permissions the merchant can grant at any time.

How the embedded UI works

When a merchant opens your app from the dashboard dock, your app’s appUrl is loaded in a sandboxed <iframe> inside the dashboard. The dashboard appends a few context parameters so your app can recognise the active store and user without a separate auth step:
These parameters are for convenience — they identify the context, not prove identity. The actual authentication proof is your signed, HttpOnly session cookie set during the OAuth install (see Install flow). Never trust shop/user from the URL without validating against your session.

Background pre-loading

The dashboard pre-loads every installed app in a hidden iframe when the merchant opens the dashboard — before the merchant ever taps the dock icon. This means:
  • Your app’s code, assets, and connections are already warm by the time the merchant opens it.
  • Background work (polling, WebSocket connections, sync) can start immediately, not only when the merchant looks at the app.
  • The first open is instant — no loading spinner if your app has initialised in the background.
When your app’s iframe loads in the background, the dashboard sends a salesive:app-wake postMessage. Register a handler for this to start background work:
The dashboard pre-loads background iframes with real dimensions (not 0×0) and off-screen positioning so browsers do not throttle their JavaScript execution. Your timers, WebSocket connections, and fetch() calls all work normally in the background.

Full lifecycle

Every embedded app goes through these events:
The app keeps running through every state change. Only an uninstall removes the iframe.

Session boundaries

Two further events fire when the ground shifts underneath your app — the merchant is no longer the same person, or is no longer looking at the same store:
Both are broadcast to every installed app, foreground or background — a backgrounded app is holding the departing merchant’s data just as wrongly as the visible one.
Handlers for these two events must be synchronous and fast. Both announce that your iframe is about to be torn down, and the dashboard only pauses briefly (~150 ms on logout) before doing it. An await on a network round trip will not finish. Clear local state synchronously; if your server must also be told, use navigator.sendBeacon(), which is designed to survive teardown.

Runtime permissions

Some capabilities — expanding the app programmatically, reading the clipboard, cancelling orders — require explicit, time-limited merchant approval. Unlike OAuth install scopes (which are approved once at install), runtime permissions can be requested at any time via a dashboard modal the merchant approves or denies. The merchant chooses a duration (once, 1 day, 7 days, etc.) and can revoke at any time.

Available runtime permissions

Runtime permissions are separate from OAuth scopes. OAuth scopes gate the backend API. Runtime permissions gate dashboard-side capabilities (UI expansion, clipboard, etc.). Your app needs both: the right scope to call an API endpoint, and the right runtime permission for any dashboard behaviour beyond rendering inside the iframe.

File picker

Your app can open the merchant’s Salesive media library and receive the files they pick — without building any upload UI or file storage of your own. Call pickFiles() and the dashboard shows the same media manager used across Salesive: the merchant selects existing files (or uploads new ones inline), and your app receives their public URLs.
The file picker needs no runtime permission — the merchant explicitly chooses each file in the modal, the same consent model as a native file input. The picker only opens while your app is in the foreground; a call from a backgrounded app resolves to an empty array. See the pickFiles() reference below for the full API.

The App Bridge package

Install the helper:
Import from the permissions subpath for the smallest bundle (tree-shakes the CLI and Vite plugin out):
Or from the main package (same exports):
TypeScript types are bundled — no @types/ package needed.

onAppWake(callback)

Fires when the dashboard first loads the app in the background. This is the earliest point at which your app’s JavaScript runs. Use it to start long-running background work.

onAppOpened(callback)

Fires each time the merchant expands the app to full view (first open or restore from minimized). Use it to refresh UI data that may be stale after time in the background.

onAppMinimized(callback)

Fires when the merchant presses − (minimize) or swipes the sheet down on mobile. The app is hidden but still running.

onAppClosed(callback)

Fires when the merchant presses × (close / dismiss). The app is still running in the background — this is the same as minimized from a lifecycle standpoint, but signals that the merchant deliberately dismissed the panel rather than just hiding it.

onLogout(callback)

Fires when the merchant logs out of Salesive. The dashboard clears only its own storage on logout. Your app runs on a different origin, so its cookies, localStorage, and tokens survive untouched — the next person to log in on that browser would silently inherit the previous merchant’s app session. This event is your only opportunity to prevent that.
Keep this handler synchronous. The dashboard sends the event, waits ~150 ms, then redirects to the login page — which destroys your iframe and anything still queued in it. await fetch("/api/session", { method: "DELETE" }) will not complete; use navigator.sendBeacon("/api/session") instead.
Your app also receives the merchant’s user id as a query param on every embed. Checking it on boot is a useful belt-and-braces companion to this event: if user differs from the one your stored session belongs to, force re-authentication regardless of whether the logout event was delivered.

onStoreChanged(callback)

Fires when the merchant switches the active store. A merchant can own several stores and swap between them without reloading the dashboard, so anything your app cached — orders, products, settings — belongs to the store they just left.
Your iframe reloads immediately after this event, with the new store’s id in the shop query param. That reload — not this callback — is what boots your app under the new store, so keep reading shop from the URL on startup exactly as you already do. Treat this callback as the “the rug is about to be pulled” hook: use it to drop caches that outlive a reload (IndexedDB, Cache Storage, localStorage) and to save in-progress work. An app that misses the event still starts up correctly against the right store; it just loses the chance to flush stale data first.

requestPermission(permission, options?, timeoutMs?)

Show the merchant a permission modal. Returns true if approved, false if denied or timed out. The modal has a 10-second auto-deny countdown.
Duration options

getGrantedPermissions(timeoutMs?)

Silently retrieve the merchant’s active grants — no modal shown. Call this on wake or mount to avoid requesting a permission the merchant already approved.
Returns a map of Permission → GrantRecord:

onLaunch()

Ask the dashboard to expand (un-minimize) this app. The call is silently ignored if AUTO_LAUNCH has not been granted.

pickFiles(options?)

Open the merchant’s Salesive media library and resolve with the files they select. Always resolves with an array of files (even in single-select mode), or an empty array if the merchant closes the picker without choosing. The picker only opens while your app is in the foreground.
size is a human-readable string ("1.2 MB"), not a raw byte count. The returned url is a hosted, publicly reachable link to the merchant’s existing media — store it or use it directly.

onEvent(type, callback)

Subscribe to any postMessage event by type string — including future event types not yet in this helper. Callbacks receive the full raw message data object.
APP_EVENTS constants

Complete React example


Environment notes

  • Browser only — all functions are no-ops in Node.js / SSR contexts (return false, {}, or a no-op unsubscribe). Safe to import in Next.js or Remix server components.
  • No runtime dependencies — the permissions subpath has zero dependencies.
  • Single listener — one window.addEventListener("message", …) is registered on module import. Multiple concurrent requestPermission() calls resolve independently.

Next steps

AI tools (WebMCP)

Register tools that Ola can call, with merchant approval.

OAuth install flow

Set up the token exchange and session binding.

Scopes & permissions

The API scopes your backend calls need.

Build & publish

Submit your app for marketplace review.

Webhooks

Real-time store events for your backend.