Skip to content

BillXola/exampleEmbeddedApps

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 

Repository files navigation

Xola Embedded App Test Apps

Reference/example embedded apps for the Xola Seller App's Embedded Apps platform, plus a guide to the @xola/embedded-app-sdk API.

An embedded app is a self-contained web page that the Seller App loads inside an iframe at a declared location (a header button, a sidebar menu item, a hidden background worker, etc.). It talks to the host Seller App over a small, versioned postMessage RPC — wrapped by the SDK — to read page context, make authenticated Xola API calls, show UI, and react to real-time events. It never shares the seller app's codebase or privileged access.

Example apps in this repo

App canvasType Demonstrates
drawerApp drawer Slide-out panel. Handshake, fetch, toasts, navigation, event log, refreshing context on canvas.open.
backgroundMonitorApp none Hidden background worker. Watches the route via context.change and surfaces itself on demand with ui.canvas.show().
fullscreenApp fullscreen Full-page app with its own internal routing/history; pulls context with xola.context.get().

Each is a plain Vite app: index.html + main.js, no framework.

Running an example locally

cd drawerApp
npm install
npm run dev          # serves on a localhost port, e.g. http://localhost:5173

The apps depend on the SDK via a local file link (file:../../x2-seller/packages/embedded-app-sdk), so the x2-seller repo must sit alongside this one and the SDK must be built (cd x2-seller/packages/embedded-app-sdk && npm install && npm run build).

To see an app inside the Seller App, register a plugin whose ui.embeddedUrl points at the local Vite URL (see Registering an app below), install it on your seller, and open the Seller App.

Firefox note: Enhanced Tracking Protection can block the app's cross-site calls to the Xola API from inside the iframe. If xola.fetch fails with a (null) status, disable ETP for the page.


SDK quick start

import { initXola } from "@xola/embedded-app-sdk";

// 1. Initialize with the Xola API version your app is written against.
const xola = initXola({ apiVersion: "2025-07-07" });

// 2. Handshake — resolves once the host has registered this iframe's session.
//    The SDK auto-retries internally, so just await it.
const hs = await xola.handshake();
// hs: { appId, sellerId, userId, roles, locale, route, context, apiUrl, appToken, timezoneName, timezoneOffset, apiVersion }

// 3. You're live. Subscribe to events, fetch data, render.

After the handshake you can call any SDK method. Calls made before it resolves queue against the pending handshake where it matters (e.g. fetch awaits it internally).


API reference

initXola({ apiVersion }) → XolaSDK

Creates the SDK instance and installs the message listener + a global error reporter (uncaught errors / unhandled rejections are forwarded to the host for telemetry). apiVersion is the Xola API date version your app targets; it's injected as x-api-version on every xola.fetch.

xola.handshake(opts?) → Promise<HandshakePayload>

Requests the session payload from the host. Resolves with the seller/user identity, current route, page context, apiUrl, and a scoped appToken. Pass { apiVersion } to override the version for this call. Auto-retries until the host responds (handles the iframe-load/registration race); rejects on a real host error or after a 30s deadline.

xola.context.get() → Promise<{ route, context }>

Pulls the current route + page context from the host (a live fetch, not a cached snapshot). Use this on canvas.open (or any time) to get fresh context without subscribing to every change. context includes route merged in.

Events — xola.events

The host pushes real-time events. You must subscribe to the event names you care about, then attach local listeners.

await xola.events.subscribe(["experience.update", "context.change", "canvas.open"]);

const off = xola.events.on("purchase.update", ({ id }) => { /* ... */ });
xola.events.on("context.change", ({ route, context }) => { /* nav happened */ });
xola.events.on("canvas.open", async () => {
    const { route, context } = await xola.context.get();   // refresh on open
});
xola.events.on("*", ({ event, data }) => { /* receive everything */ });

off();                                  // on() returns an unsubscribe fn
xola.events.off("purchase.update", cb); // or remove explicitly
await xola.events.unsubscribe(["experience.update"]);

Event names (XolaEvent constants):

Name Data Fires when
context.change { route, context } Host route/context changes (navigation).
canvas.open { route, context } This app's drawer/modal is opened. Subscribe to this to refresh on open instead of following every context.change.
experience.update { id } An experience changed.
purchase.update { id } A purchase changed.
package.update { id } A package changed.
event.update { id } A scheduled event/timeslot changed.
liveChat.update {} Live-chat availability changed.
xchatDisabled.update { enabled } XChat toggled.
auth.tokenRefreshed { token } SDK refreshed the auth token (rebuild long-lived connections).
* { event, data } Wildcard — every event.

UI — xola.ui

xola.ui.toast("success", "Saved!");          // type: "success" | "info" | "warning" | "error"
xola.ui.drawer.close();                       // dismiss a drawer canvas
xola.ui.modal.close();                         // dismiss a modal canvas

// canvasType=none (background) apps only — present/hide self as a drawer:
await xola.ui.canvas.show({ canvasPresentation: "push", canvasLocation: "right", canvasSize: "medium" });
await xola.ui.canvas.hide();

Auth & data — xola.auth, xola.fetch

const token = await xola.auth.getToken();              // cached; auto-refreshes near expiry
const token2 = await xola.auth.getToken({ force: true }); // bypass cache

// fetch injects x-api-key / x-seller-id / x-user-id / x-app / x-api-version headers
// and the seller query param, retries once on 401, and returns parsed JSON.
const me = await xola.fetch("/api/users/me");
const created = await xola.fetch("/api/something", { method: "POST", body: JSON.stringify({...}) });

Prefer xola.fetch over managing tokens yourself. For SSE/WebSocket connections that outlive a token, listen for auth.tokenRefreshed and reconnect.

Navigation — xola.navigate(route, opts?)

xola.navigate("/orders");
xola.navigate("/orders", { replace: true });

Host-mediated React-router navigation. Note: silently ignored for third-party (non-first-party) apps.


App lifecycle you should design for

The host keeps your iframe alive across opens — a drawer app is loaded once and reused, not reloaded each time it's opened. Consequences:

  • Do the handshake once on startup.
  • The context you got at handshake goes stale after the user navigates and reopens you from a different screen. Subscribe to canvas.open and refresh via xola.context.get() (or read the event payload) so you always reflect the screen you were opened from. See drawerApp/main.js.
  • Don't assume a fresh page per open; persist your own in-app state as you see fit.

Context shape

Context is a nested hierarchy of { id, <childrenPlural>: [...] }, children always arrays. Examples by location:

// Roster header
{ events: [{ id, purchases: [{ id, items: [{ id, guests: [{ id }] }] }] }] }
// Purchase detail
{ purchases: [{ id, items: [{ id, guests: [{ id }] }] }] }
// A single-entity detail page
{ customers: [{ id }] }   // or { partners: [{ id }] }, { guides: [{ id }] }, ...

route is always available alongside the context.


Registering an app (plugin config)

An embedded app is a Plugin whose ui block carries the embedded-app fields (managed in elrond / Store Console):

Field Type Notes
isEmbeddedApp boolean Marks the plugin as an embedded app.
isFirstParty boolean First-party (Xola-authored) apps get extra privileges (e.g. navigate).
embeddedUrl string Origin/URL the iframe loads (your hosted app, or local Vite URL in dev).
canvasType none | drawer | modal | fullscreen How/where the app surface renders.
canvasSize small | medium | large | xl | 2xl Drawer/modal size.
canvasLocation left | right Drawer side.
canvasPresentation overlay | push Drawer overlays content or pushes it aside.
triggers [{ type, location, label, icon }] Entry points. type: button | menuItem | automatic. location: a trigger-location id (below).
requestedContextFields [string] Context fields the app needs.
apiScopes [string] API scopes the app requires.

Canvas types: drawer/modal/fullscreen render a visible surface; none is a hidden background worker (must use triggerType: automatic, and can surface itself via ui.canvas.show). fullscreen requires a menuItem trigger.

Example trigger-location ids: dashboard.headerBar, dashboard.roster.headerBar, purchases.detail.headerBar, purchases.detail.buttonBar, customers.contacts.headerBar, distribution.operators.detail.headerBar, resources.equipment.detail.headerBar, customers.leftMenu, settings.leftMenu, … (a button location renders a button; a leftMenu location renders a sidebar item).


Create your own embedded app

  1. Scaffold a Vite app (copy drawerApp as a starting point): index.html + main.js, add @xola/embedded-app-sdk as a dependency.
  2. On load: const xola = initXola({ apiVersion }); const hs = await xola.handshake();
  3. Render from hs.context / hs.route; subscribe to the events you need; for drawer/modal apps, refresh on canvas.open.
  4. Make API calls with xola.fetch(...).
  5. Host it somewhere the Seller App can reach, register a plugin pointing ui.embeddedUrl at it with the right canvasType/triggers, install it on a seller.

See the three example apps for working patterns.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors