E EMBAN / Docs

React Integration

React has two honest Emban paths: a signed iframe helper for dashboard-level embeds, and a native component SDK for widget-level composition.

Integration split: @emban/sdk stays server-side. Use @emban/embed-helper/react when you want the signed iframe plus runtime methods and events. Use @emban/react when you want native React widgets or a dashboard grid rendered without iframe.

1. Backend: create embed session

Your backend should create the signed tenant session and return the values your frontend path needs. The browser never gets the admin key.

// api/analytics.ts (your backend)
import { EmbanClient } from '@emban/sdk';

const adminClient = new EmbanClient({
  baseUrl: 'https://emban.sidelabs.dev',
  apiKey: process.env.EMBAN_ADMIN_KEY!,
});

app.get('/api/analytics/embed', async (req, res) => {
  const dashboardId = process.env.EMBAN_DASHBOARD_ID!;

  const session = await adminClient.createEmbedSession({
    tenantId: req.user.orgId,
    dashboardId,
    expiresIn: 3600,
    permissions: {
      allowDrillDown: true,
      allowedDimensions: ['model', 'endpoint'],
      allowedPeriods: ['24h', '7d', '30d'],
    },
  });

  res.json({
    embed_url: session.embedUrl,
    token: session.token,
    dashboard_id: dashboardId,
    host: 'https://emban.sidelabs.dev',
  });
});

2. Path A: signed iframe helper

Use this when you want the published customer-facing dashboard and a thin browser runtime on top.

import { useEffect, useState } from 'react';
import { EmbanEmbedFrame, useEmbanEmbed } from '@emban/embed-helper/react';

function AnalyticsDashboard() {
  const [embedUrl, setEmbedUrl] = useState('');

  useEffect(() => {
    fetch('/api/analytics/embed')
      .then(r => r.json())
      .then(({ embed_url }) => setEmbedUrl(embed_url));
  }, []);

  if (!embedUrl) return <div>Loading analytics...</div>;

  return (
    <EmbanEmbedFrame
      embedUrl={embedUrl}
      containerStyle={{ minHeight: 600 }}
      onReady={event => console.log('ready', event.dashboardId)}
      onFilter={event => console.log('filters', event.period, event.filters)}
      onPeriodChange={event => console.log('period', event.previousPeriod, event.period)}
      onSessionSwapped={event => console.log('session', event.previousEmbedUrl, event.embedUrl)}
    />
  );
}

function RuntimeControls({ embedUrl }: { embedUrl: string }) {
  const { containerRef, embed } = useEmbanEmbed({ embedUrl });

  return (
    <>
      <div ref={containerRef} style={{ minHeight: 600 }} />
      <button onClick={() => embed?.setFilters({ period: '7d' })}>Last 7 days</button>
      <button onClick={() => embed?.reload()}>Reload</button>
    </>
  );
}

3. Iframe helper methods and events

MethodDescription
Emban.create(options)Mount dashboard into container
<EmbanEmbedFrame embedUrl={url} />React wrapper around the browser helper
dash.setEmbedUrl(nextSignedUrl)Swap the signed tenant-scoped embed session without recreating the wrapper
dash.setFilters({ period: '7d', dimensions: { model: 'gpt-4' } })Update period, dimension filters, and optional numeric/date ranges
dash.setTheme({primaryColor: '#000'})Change theme colors
dash.reload()Force data refresh
dash.on('ready', fn)Fires when dashboard renders
dash.on('filter', fn)Reports the full active filter state, including period and active drill filters
dash.on('period-change', fn)Reports period transitions inside the embed
dash.on('session-swapped', fn)Reports signed session rotation when setEmbedUrl() replaces the current URL
dash.on('drill', fn)Reports apply/clear/detail drill actions
Session refresh: if your backend rotates the signed session or switches to a different tenant-scoped session, keep the same helper instance and call dash.setEmbedUrl(nextSignedUrl) instead of tearing the wrapper down.

4. Commands and events

The helper wraps a plain postMessage contract. If you want the higher-level model first, read Embed Runtime. If you inspect the live demo or build a very thin host runtime yourself, these are the payloads the browser layer uses.

// Host page -> embed runtime
{
  "_emban": true,
  "type": "setFilters",
  "filters": { "period": "7d" }
}

{
  "_emban": true,
  "type": "setTheme",
  "theme": {
    "primaryColor": "#0f9d58",
    "backgroundColor": "#f7f9f7",
    "cardBackground": "#ffffff",
    "cardBorder": "#d8e2d9",
    "textColor": "#101513",
    "mutedColor": "#5d6c63"
  }
}

{
  "_emban": true,
  "type": "reload"
}

// Embed runtime -> host page
{
  "_emban": true,
  "type": "ready",
  "dashboardId": "dash_123"
}

{
  "_emban": true,
  "type": "period-change",
  "previousPeriod": "30d",
  "period": "7d"
}

{
  "_emban": true,
  "type": "filter",
  "period": "7d",
  "filters": { "model": "gpt-4" },
  "numericRanges": {},
  "dateRanges": {}
}

{
  "_emban": true,
  "type": "drill",
  "dimension": "endpoint",
  "value": "/chat",
  "action": "apply"
}

{
  "_emban": true,
  "type": "error",
  "message": "Couldn't refresh this dashboard"
}
One boundary, two surfaces: raw iframe runtime emits ready, resize, filter, period-change, drill, and error. The helper also emits session-swapped on the host side when setEmbedUrl() replaces the signed URL.

5. Path B: native React components

Use this when you want native composition in your app layout instead of iframe. The native path still uses the same signed tenant token from your backend.

import { EmbanDashboard, EmbanProvider, EmbanWidget, useEmbanRuntime } from '@emban/react';

function RefreshButton() {
  const runtime = useEmbanRuntime();
  return <button onClick={() => runtime.refetch()}>Refresh widgets</button>;
}

function NativeAnalytics({ session }) {
  return (
    <EmbanProvider
      host={session.host}
      token={session.token}
      dashboardId={session.dashboardId}
      filters={{ period: '30d' }}
    >
      <section style={{ display: 'grid', gap: 16 }}>
        <EmbanWidget
          widgetId="requests_kpi"
          height={180}
          onDrillEvent={event => console.log(event.dimension, event.value, event.rawPoint)}
        />
        <EmbanDashboard
          rowHeight={72}
          onDrillEvent={event => console.log(event.widgetId, event.label)}
        />
        <RefreshButton />
      </section>
    </EmbanProvider>
  );
}
Which path to pick? Use @emban/embed-helper/react for the published dashboard and tenant-scoped session lifecycle. Use @emban/react when your product needs widget-level composition, native chart rendering, or provider-level runtime control.