Skip to content

Host bridge (iframe contract)

Surface Apps load in a cross-origin iframe inside Helix Platform Desktop. Some browser APIs are blocked in that context. Apps talk to the shell over postMessage using the host bridge contract (defined in @helix-platform/sharedhost-bridge.ts).

When to use it

CapabilityCall from iframe?Use bridge instead
Cognito JWT for Log Center / shell APIN/ASession messages
Notification.requestPermission()No (blocked cross-origin)Notification permission request
Web Push subscribe (VAPID)Prefer PlatformOptional enablePush on the same request

Platform stays a shell: it grants permission / owns the service worker; product logic stays in the Surface App.

Message types

DirectiontypePurpose
Platform → apphelix-platform-session{ accessToken, apiBaseUrl }
App → Platformhelix-platform-session-requestAsk parent to (re)send session
App → Platformhelix-platform-notification-permission-request{ requestId, enablePush? }
Platform → apphelix-platform-notification-permission-result{ requestId, permission, pushEnabled?, error? }
App → Platformhelix-platform-toast{ title?, body, variant?, durationMs? } — ephemeral shell toast

Shell toasts (app side)

Apps embedded in Platform should not show persistent inline status for shell-mediated actions (e.g. notification permission). Post a toast instead — Platform shows a short-lived popover near the taskbar.

ts
window.parent.postMessage(
  {
    type: 'helix-platform-toast',
    title: 'Helix Patch',
    body: 'Scan completed.',
    variant: 'success', // info | success | warning | error
    durationMs: 6000,
  },
  '*',
)

Fire-and-forget — no reply. When requesting notification permission, Platform also shows toasts for granted / denied automatically.

Notification permission (app side)

ts
function isEmbedded(): boolean {
  try {
    return window.parent != null && window.parent !== window
  } catch {
    return true
  }
}

export async function requestNotificationPermission(opts?: {
  enablePush?: boolean
  timeoutMs?: number
}): Promise<{ permission: NotificationPermission; pushEnabled: boolean }> {
  if (!isEmbedded()) {
    const permission = await Notification.requestPermission()
    return { permission, pushEnabled: false }
  }
  const requestId = crypto.randomUUID()
  return new Promise((resolve, reject) => {
    const t = window.setTimeout(() => {
      window.removeEventListener('message', onMessage)
      reject(new Error('Platform did not respond to notification permission request'))
    }, opts?.timeoutMs ?? 120_000)
    function onMessage(event: MessageEvent) {
      const data = event.data as {
        type?: string
        requestId?: string
        permission?: NotificationPermission
        pushEnabled?: boolean
        error?: string
      }
      if (data?.type !== 'helix-platform-notification-permission-result') return
      if (data.requestId !== requestId) return
      window.clearTimeout(t)
      window.removeEventListener('message', onMessage)
      if (data.error && data.permission !== 'granted') {
        reject(new Error(data.error))
        return
      }
      resolve({
        permission: data.permission ?? 'default',
        pushEnabled: Boolean(data.pushEnabled),
      })
    }
    window.addEventListener('message', onMessage)
    window.parent.postMessage(
      {
        type: 'helix-platform-notification-permission-request',
        requestId,
        enablePush: opts?.enablePush !== false,
      },
      '*',
    )
  })
}

Standalone (top-level) apps keep calling Notification.requestPermission() directly — the helper above detects embedding.

Platform host

HelixAppFrame listens for bridge requests, runs Notification.requestPermission() in the top-level frame, optionally registers Platform Web Push, and replies to event.source.

See also

  • Shell API — HTTP notifications / push subscribe
  • Shared types: shared/src/host-bridge.ts

Helix Platform · Igentify