Session & bridge

Connect to the parent portal and read the session: token, config, theme, and language.

The bridge API is how your block connects to the portal and learns about its environment. The center of it is the session: a single object describing the authenticated context your block runs in.

initialize(options?)

Performs the postMessage handshake with the parent portal and resolves to a SparkSession. Call it once at startup, before any entity or use-case call.

import { initialize } from '@epilot/spark-sdk'

const session = await initialize()

Key behaviors:

  • Idempotent: repeat calls return the cached session; concurrent calls share one in-flight handshake. You can call it from multiple modules without coordinating.
  • Rejects on timeout: if the portal doesn't respond within timeout (default 5000ms) it throws a SparkTimeoutError. A failed init clears the cache, so a later retry will attempt a fresh handshake.
  • Side effects on success: injects the portal's theme CSS variables, sets the light/dark class, and subscribes to future theme, locale, and viewport updates.

InitOptions

OptionTypeDefaultDescription
timeoutnumber5000Milliseconds to wait for the portal's init response before rejecting.
apiBaseUrlstringfrom portalOverride the API base URL. By default it's taken from the portal's init response.
allowedParentOriginsstring[][]Origins the bridge will trust. When set, inbound messages from other origins are dropped and outbound messages target the single configured origin instead of *. Empty = accept any parent (legacy).
const session = await initialize({
  timeout: 8000,
  allowedParentOrigins: ['https://portal.epilot.io'],
})

Security tip: in production, pass allowedParentOrigins so the bridge only trusts the real portal origin. Without it, the bridge accepts messages from any parent and posts to *.

getSession()

Returns the current session with a freshly-fetched auth token. The token is requested from the portal on every call and never cached on the block side, so each returned session carries a current token. Rejects with SparkNotInitializedError if called before initialize() has resolved. Use it in code paths that run only after startup (most of your app).

import { getSession } from '@epilot/spark-sdk'

const { token, apiBaseUrl } = await getSession()

In React, prefer reading the session from context (see Getting started) so components re-render when it's ready.

isInitialized()

Returns true once a session exists. Handy for guards that shouldn't throw.

import { isInitialized } from '@epilot/spark-sdk'

if (isInitialized()) {
  // safe to call getSession()
}

The SparkSession object

interface SparkSession {
  /** Portal auth token for API calls */
  token: string
  /** Portal API base URL */
  apiBaseUrl: string
  /** User's language preference */
  lang?: string
  /** Organization ID */
  orgId?: string
  /** Portal ID */
  portalId?: string
  /** CSS variables string for theming (injected into document head) */
  cssVariables?: string
  /** Current theme mode */
  theme?: 'light' | 'dark'
  /** App-level configuration from the installation (e.g., integration_id) */
  config?: Record<string, unknown>
}

The SDK keeps theme and lang on the cached session in sync as the portal pushes updates, so reading (await getSession()).lang always reflects the current language.

config: your installation settings

config is the per-installation configuration the portal admin set up for your block. Its shape is up to your block; cast the fields you expect. A HEMS reads its Integration Hub id, target entities, and a mock-mode flag:

// app/providers/block-context-provider.tsx
const session = useSparkSession()

const integrationId = session.config?.integration_id as string | undefined
const entityIds = (session.config?.entityIds ?? {}) as Record<string, string>
const assetId = entityIds.asset
const isMockedMode = session.config?.is_mocked_mode === true

integrationId is the input every use-case call needs; isMockedMode is how the block renders mock data in the block editor without calling real integrations.

Reporting content size

The portal sizes your iframe from what you report: it can't measure across the origin boundary. Call these whenever your layout changes (data loads, routes switch, panels expand). See Getting started for the ResizeObserver pattern.

updateContentHeight

updateContentHeight(height: number): void
import { updateContentHeight } from '@epilot/spark-sdk'

updateContentHeight(document.body.scrollHeight)

updateContentSize

Reports width and/or height. Use this for footer blocks or absolutely-positioned content where the iframe must match the full dimensions.

updateContentSize(size: { height?: number; width?: number }): void
import { updateContentSize } from '@epilot/spark-sdk'

updateContentSize({
  width: document.documentElement.scrollWidth,
  height: document.documentElement.scrollHeight,
})

See also

On this page