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:
https://your-app.com/embed
  ?shop=<store-id>
  &name=<store-name>
  &user=<salesive-user-id>
  &embedded=1
  &host=https://app.salesive.com
ParameterDescription
shopThe installed store’s id. Use this to look up your stored OAuth tokens and make API calls on behalf of this store.
nameThe store’s display name. Useful for greeting the merchant before your API call returns.
userThe Salesive user id of the merchant currently using the dashboard. Check this against your session to detect account switches (see session binding).
embeddedAlways "1". Use this to detect the embedded context and hide your public marketing shell.
hostThe dashboard’s origin (https://app.salesive.com).
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:
import { onAppWake } from "salesive-dev-tools/permissions";

onAppWake(() => {
  connectWebSocket();
  startOrderPolling();
  prefetchCriticalData();
});
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:
dashboard opens


 salesive:app-wake          ← iframe loaded in background; start sync, sockets, etc.

      │  (merchant taps dock icon)

 salesive:app-opened        ← app is now visible; show fresh UI, stop any "background only" modes

      │  (merchant presses − or swipes down)

 salesive:app-minimized     ← app hidden but still running; pause heavy rendering work

      │  (merchant reopens)

 salesive:app-opened        ← visible again

      │  (merchant presses × / close)

 salesive:app-closed        ← dashboard has told the app it was dismissed; save unsaved state
                               (app is still running in background — wake/open may follow)
The app keeps running through every state change. Only an uninstall removes the iframe.

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

PermissionWhat it lets your app do
AUTO_LAUNCHCall onLaunch() to expand the app from background without the merchant tapping the dock.
WRITE_DOMAINSAdd and remove custom domains linked to the merchant’s store.
CLIPBOARD_READRead text from the merchant’s system clipboard.
WRITE_ORDERS_CANCELCancel orders on the merchant’s behalf (irreversible).
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.

The App Bridge package

Install the helper:
npm install salesive-dev-tools
Import from the permissions subpath for the smallest bundle (tree-shakes the CLI and Vite plugin out):
import {
  PERMISSIONS,
  DURATIONS,
  APP_EVENTS,
  requestPermission,
  getGrantedPermissions,
  onLaunch,
  onAppWake,
  onAppOpened,
  onAppMinimized,
  onAppClosed,
  onEvent,
} from "salesive-dev-tools/permissions";
Or from the main package (same exports):
import { requestPermission, onAppWake } from "salesive-dev-tools";
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.
function onAppWake(callback: () => void): () => void  // returns unsubscribe
import { onAppWake } from "salesive-dev-tools/permissions";

onAppWake(async () => {
  await connectWebSocket();
  const grants = await getGrantedPermissions();
  if (PERMISSIONS.AUTO_LAUNCH in grants) scheduleAutoLaunchTimer();
});

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.
function onAppOpened(callback: () => void): () => void
onAppOpened(() => {
  refreshDashboard();
  clearBadge();
});

onAppMinimized(callback)

Fires when the merchant presses − (minimize) or swipes the sheet down on mobile. The app is hidden but still running.
function onAppMinimized(callback: () => void): () => void
onAppMinimized(() => {
  pauseHeavyAnimations();
  reducePollingFrequency();
});

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.
function onAppClosed(callback: () => void): () => void
onAppClosed(() => {
  saveDraft();
  cancelPendingAnimations();
});

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.
function requestPermission(
  permission : Permission,
  options?   : DurationOption[],  // duration choices; omit = all five. First = default.
  timeoutMs? : number,            // default 30 000 ms
): Promise<boolean>
import { PERMISSIONS, DURATIONS, requestPermission } from "salesive-dev-tools/permissions";

// Check existing grant first — no modal shown for this call:
const grants = await getGrantedPermissions();

if (!(PERMISSIONS.AUTO_LAUNCH in grants)) {
  // Request — show 7-day and 31-day options:
  const granted = await requestPermission(
    PERMISSIONS.AUTO_LAUNCH,
    [DURATIONS.SEVEN_DAYS, DURATIONS.THIRTY_ONE_DAYS]
  );
  if (!granted) return; // merchant denied
}
Duration options
ConstantStringLasts
DURATIONS.ONCE"once"Consumed on first exercise
DURATIONS.ONE_DAY"1day"1 day
DURATIONS.SEVEN_DAYS"7days"7 days
DURATIONS.FOURTEEN_DAYS"14days"14 days
DURATIONS.THIRTY_ONE_DAYS"31days"31 days

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.
function getGrantedPermissions(timeoutMs?: number): Promise<PermissionGrantMap>
Returns a map of Permission → GrantRecord:
interface GrantRecord {
  mode      : "once" | "1day" | "7days" | "14days" | "31days";
  grantedAt : string;           // ISO-8601 timestamp
  expiresAt : string | null;    // null for "once" grants
  usedAt    : string | null;    // set after a "once" grant is consumed
}
onAppWake(async () => {
  const grants = await getGrantedPermissions();

  if (PERMISSIONS.AUTO_LAUNCH in grants) {
    // Merchant already approved — set up the timer directly
    startAutoLaunchTimer(grants[PERMISSIONS.AUTO_LAUNCH].expiresAt);
  }
});

onLaunch()

Ask the dashboard to expand (un-minimize) this app. The call is silently ignored if AUTO_LAUNCH has not been granted.
// Auto-reopen after 30 s when minimized and AUTO_LAUNCH is granted:
onAppMinimized(() => {
  const timer = setTimeout(onLaunch, 30_000);
  const unsub = onAppOpened(() => { clearTimeout(timer); unsub(); });
});

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.
function onEvent(
  type     : string,
  callback : (data: Record<string, unknown>) => void,
): () => void
import { APP_EVENTS, onEvent } from "salesive-dev-tools/permissions";

// Forward all lifecycle events to your analytics:
for (const type of Object.values(APP_EVENTS)) {
  onEvent(type, (data) => analytics.track(data.type, data));
}
APP_EVENTS constants
ConstantEvent type stringWhen it fires
APP_EVENTS.WAKEsalesive:app-wakeBackground iframe first loaded
APP_EVENTS.OPENEDsalesive:app-openedApp expanded to full view
APP_EVENTS.MINIMIZEDsalesive:app-minimizedMerchant pressed −
APP_EVENTS.CLOSEDsalesive:app-closedMerchant pressed ×
APP_EVENTS.PERMISSION_RESPONSEsalesive:permission-responseResponse to requestPermission()
APP_EVENTS.PERMISSIONS_STATEsalesive:permissions-stateResponse to getGrantedPermissions()

Complete React example

import { useEffect, useState } from "react";
import {
  PERMISSIONS, DURATIONS, APP_EVENTS,
  requestPermission, getGrantedPermissions,
  onLaunch, onAppWake, onAppOpened, onAppMinimized, onAppClosed,
} from "salesive-dev-tools/permissions";

export default function App() {
  const [autoLaunchGranted, setAutoLaunchGranted] = useState(false);

  // Background init — fires before merchant opens the app
  useEffect(() => {
    return onAppWake(async () => {
      // Start background work immediately
      connectWebSocket();

      // Check existing grants silently
      const grants = await getGrantedPermissions();
      setAutoLaunchGranted(PERMISSIONS.AUTO_LAUNCH in grants);
    });
  }, []);

  // Foreground lifecycle
  useEffect(() => {
    const unOpen = onAppOpened(() => refreshDashboard());
    const unMin  = onAppMinimized(() => pauseHeavyWork());
    const unCls  = onAppClosed(() => saveDraft());
    return () => { unOpen(); unMin(); unCls(); };
  }, []);

  // Auto-reopen when minimized (only if granted)
  useEffect(() => {
    if (!autoLaunchGranted) return;
    return onAppMinimized(() => {
      const t = setTimeout(onLaunch, 30_000);
      const u = onAppOpened(() => { clearTimeout(t); u(); });
    });
  }, [autoLaunchGranted]);

  async function handleRequestAutoLaunch() {
    const ok = await requestPermission(
      PERMISSIONS.AUTO_LAUNCH,
      [DURATIONS.SEVEN_DAYS, DURATIONS.THIRTY_ONE_DAYS]
    );
    setAutoLaunchGranted(ok);
  }

  return (
    <div>
      {autoLaunchGranted
        ? <p>✓ Auto-launch active — app will reopen when minimized.</p>
        : <button onClick={handleRequestAutoLaunch}>Enable auto-reopen</button>
      }
    </div>
  );
}

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

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.