EMBEDDABLE BRIDGE

Ecosistema completo para organización y gestión de contenido con interfaces espaciales

Embeddable Bridge Architecture

How GoGrab AI is embedded across different host applications, Chrome extensions, and standalone contexts.

Library: @libs-ux/embeddable-bridge Path: libs/libs-ux/embeddable-bridge/ Last updated: Session 105.3 (February 2026)


Table of Contents

  1. Overview
  2. Architecture Diagram
  3. Three Embedding Approaches
  4. URL Parameters Reference
  5. Workspace Port Derivation
  6. Bridge Event Model
  7. Authentication Flows
  8. Component Reference
  9. Comparison Table
  10. Troubleshooting

Overview

The Embeddable Bridge library enables the gograb-ai Angular application to be embedded inside other applications as an iframe with structured, type-safe communication. It provides:

The gograb-ai application serves two main routes for embedding:

Route Purpose
/panel AI Creation Studio -- chat, image, video, widget, module generation
/observatory Agent Observatory -- real-time monitoring of Vitto and Orchestration agents

Architecture Diagram

+---------------------------+       +---------------------------+
|  HOST APPLICATION         |       |  gograb-ai App            |
|  (gograb-space, admin,    |       |  (separate Angular app)   |
|   dev, or extension)      |       |                           |
|                           |       |  Routes:                  |
|  +---------------------+ |       |  /panel   -> Studio       |
|  | GoGrabAiPanelComponent|----+   |  /observatory -> Monitor  |
|  | or                   | |   |   |                           |
|  | GoGrabObservatoryComp| |   |   |  +---------------------+ |
|  +---------------------+ |   |   |  | EmbeddedAdapterSvc   | |
|           |               |   |   |  | - Listens for msgs   | |
|  +---------------------+ |   |   |  | - Sends replies       | |
|  | EmbeddableHostComp   | |   |   |  | - URL param auth      | |
|  | - Creates <iframe>   | |   |   |  +---------------------+ |
|  | - HostAdapterService | |   |   +---------------------------+
|  | - Origin validation  |-----|-->   iframe (sandboxed)
|  | - Heartbeat monitor  | |   |      src="http://localhost:
|  +---------------------+ |   |       6400/panel?embedded=true
|                           |   |       &theme=dark&..."
+---------------------------+   |
                                |
    PostMessage Bridge  <-------+
    (bidirectional, typed)

Three Embedding Approaches

1. Direct Component (Native)

Used when the AI panel lives in the same Angular application as the host.

Since Session 105.3, the native panel uses <ai-panel-tabs> which wraps both <lib-ai-panel> (Studio) and <agent-observatory> (Observatory) with a tab switcher.

2. Embeddable Bridge (Cross-App Iframe)

Used when embedding the AI panel from a different Angular application via iframe.

3. Chrome Extension Iframe

Used when embedding inside the Chrome extension's side panel.


URL Parameters Reference

When gograb-ai is loaded in an iframe, it reads parameters from the URL to configure its behavior.

/panel Route (Studio)

Parameter Type Default Description
embedded 'true' / 'false' 'false' Signals the app is running inside an iframe. Hides standalone header, adjusts background.
extension 'true' / 'false' 'false' Signals Chrome extension context. Triggers URL-param auth flow instead of waiting for bridge auth.
theme 'dark' / 'light' 'dark' Visual theme override for the panel.
userRole 'developer' / 'builder' / 'user' 'user' Maps to tenant tier: developer = T1 (full access), builder = T2 (space builder), user = T3 (platform user).
hostApp string — Identifies the host application: 'space', 'admin', 'dev', 'ai', 'space-admin'.
token string — JWT access token for authentication. Used in extension context where bridge auth is unreliable.
userId string — User ID for authentication context.
userEmail string — User email for display and identification.
userName string — User display name.
spaceId string — Current space context. Enables space-scoped operations and permissions.
source string — Origin identifier: 'embed-component' (from Angular bridge), 'sidepanel' (from extension).
instanceId string — Optional instance ID for multi-instance support.

Example URL:

http://localhost:6400/panel?embedded=true&extension=true&theme=dark&userRole=developer

This URL means:

/observatory Route

Parameter Type Default Description
embedded 'true' / 'false' 'false' Same as panel.
extension 'true' / 'false' 'false' Same as panel.
theme 'dark' / 'light' 'dark' Visual theme.
mode 'compact' / 'full' 'compact' compact = single-column (side panel), full = multi-column dashboard.
controls 'true' / 'false' 'true' Show/hide control buttons (submit task, etc.).
spaceId string — Space context.
source string — Origin identifier.
token, userId, userEmail, userName string — Auth (same as panel).

Example URL:

http://localhost:6400/observatory?embedded=true&mode=compact&controls=true&source=embed-component

Workspace Port Derivation

Each development workspace occupies a 1000-port range. The AI app is always at offset +400:

Workspace Port Range AI Port Example Host
editor2 5000-5999 5400 gograb-space at 5200
gspace1 6000-6999 6400 gograb-space at 6200
gograb-modules 4000-4999 4400 gograb-admin at 4201

How deriveAiPort() Works

Located in libs/libs-ux/embeddable-bridge/src/lib/utils/workspace-ports.ts:

const AI_PORT_OFFSET = 400;

export function deriveAiPort(fallbackPort = 5400): number {
  if (typeof window === 'undefined') return fallbackPort;

  const hostPort = parseInt(window.location.port, 10);
  if (isNaN(hostPort) || hostPort < 4000 || hostPort > 9999) {
    return fallbackPort;
  }

  const workspaceBase = Math.floor(hostPort / 1000) * 1000;
  return workspaceBase + AI_PORT_OFFSET;
}

Logic:

  1. Read the current browser window's port (e.g., 6200)
  2. Compute the workspace base: Math.floor(6200 / 1000) * 1000 = 6000
  3. Add the AI offset: 6000 + 400 = 6400

Fallback: If running in SSR, or port is outside 4000-9999, returns fallbackPort (default: FRONTEND_PORTS.gograbAi = 5400).

Extension Port Resolution

The Chrome extension cannot use deriveAiPort() because it runs in its own context (no Angular host port). Instead, sidepanel.js probes known ports sequentially:

const KNOWN_AI_PORTS = [5400, 6400, 4200];

async function resolveAiAppUrl() {
  for (const port of KNOWN_AI_PORTS) {
    try {
      const resp = await fetch(`http://localhost:${port}/`, { mode: 'no-cors' });
      return `http://localhost:${port}`;
    } catch { continue; }
  }
}

Bridge Event Model

All communication between host and embedded app uses typed BridgeMessage objects sent via window.postMessage:

interface BridgeMessage<T = unknown> {
  type: BridgeMessageType;
  payload?: T;
  id?: string;
  timestamp: number;
  origin?: string;
  instanceId?: string;
}

Message Categories

Lifecycle

Type Direction Description
AI_APP_READY Embedded -> Host App is loaded and ready to receive messages
AI_APP_DESTROYED Embedded -> Host App is being destroyed
AI_PING Host -> Embedded Heartbeat check
AI_PONG Embedded -> Host Heartbeat response
AI_ACK Both Acknowledgment of a received message
AI_HEARTBEAT Host -> Embedded Periodic keep-alive
AI_RECONNECT Host -> Embedded Request to re-establish connection

Authentication

Type Direction Description
AI_AUTH_INIT Host -> Embedded Send initial auth credentials
AI_AUTH_REFRESH Host -> Embedded Send refreshed token
AI_AUTH_LOGOUT Host -> Embedded Notify logout
AI_AUTH_REQUEST_REFRESH Embedded -> Host Request token refresh

Context

Type Direction Description
AI_CONTEXT_UPDATE Host -> Embedded Send updated page/feature context
AI_CONTEXT_REQUEST Embedded -> Host Request current context

Drag and Drop

Type Direction Description
AI_DRAG_START Embedded -> Host User started dragging an output
AI_DRAG_MOVE Embedded -> Host Drag position update
AI_DRAG_END Embedded -> Host Drag ended (with drop data)
AI_DROP_SUCCESS Host -> Embedded Drop was accepted by host canvas
AI_DROP_CANCELLED Host -> Embedded Drop was cancelled/rejected

Prompts and Responses

Type Direction Description
AI_PROMPT Host -> Embedded Send a prompt to the AI
AI_RESPONSE Embedded -> Host AI response complete

Streaming

Type Direction Description
AI_STREAM_START Embedded -> Host Streaming response started
AI_STREAM_CHUNK Embedded -> Host Streaming chunk received
AI_STREAM_END Embedded -> Host Streaming completed
AI_STREAM_ERROR Embedded -> Host Streaming error

Panel State

Type Direction Description
AI_PANEL_COLLAPSE Host -> Embedded Panel was collapsed
AI_PANEL_EXPAND Host -> Embedded Panel was expanded
AI_PANEL_STATE Both Panel state synchronization

Errors

Type Direction Description
AI_ERROR Embedded -> Host General error
AI_ERROR_BOUNDARY Embedded -> Host Component error boundary triggered

Authentication Flows

Flow A: URL Parameters (Extension)

Used when extension=true is in the URL. This is the primary auth mechanism for the Chrome extension because postMessage auth can be unreliable in the extension context.

Chrome Extension                    gograb-ai App
      |                                   |
      |  iframe src=".../panel?           |
      |    extension=true&                |
      |    token=JWT&userId=X&            |
      |    userEmail=Y"                   |
      |---------------------------------->|
      |                                   |
      |                          initializeFromExtensionParams()
      |                          reads token, userId, userEmail
      |                          from URL, sets auth state
      |                                   |
      |          AI_APP_READY             |
      |<----------------------------------|

Flow B: Bridge PostMessage (Angular Embeds)

Used by GoGrabAiPanelComponent and GoGrabObservatoryComponent when embedding in Angular apps.

Host Angular App                    gograb-ai App (iframe)
      |                                   |
      |  <iframe src=".../panel?          |
      |    embedded=true">                |
      |---------------------------------->|
      |                                   |
      |          AI_APP_READY             |
      |<----------------------------------|
      |                                   |
      |  If autoSendOnReady:              |
      |  AI_AUTH_INIT { token, user }     |
      |---------------------------------->|
      |                          sets auth state from payload
      |                                   |
      |  ...later...                      |
      |  AI_AUTH_REQUEST_REFRESH          |
      |<----------------------------------|
      |                                   |
      |  AI_AUTH_REFRESH { newToken }     |
      |---------------------------------->|

Flow C: Shared Session (Direct Component)

When using <lib-ai-panel> directly in gograb-space, there is no bridge. The panel runs in the same Angular injector tree and shares the same AuthService, HttpClient, and cookie-based session. No special auth wiring needed.


Component Reference

EmbeddableHostComponent

Selector: embeddable-host Library: @libs-ux/embeddable-bridge Role: Creates and manages the iframe, wires up the bridge.

Input Type Default Description
embeddedUrl string — Full URL to load in iframe
allowedOrigins string[] [] Origins allowed for PostMessage
width string '100%' Iframe width
height string '100%' Iframe height
title string 'Embedded App' Iframe title (accessibility)
enableDragDrop boolean false Enable drag-drop from iframe
enableHeartbeat boolean true Enable heartbeat monitoring
autoSendAuth boolean false Auto-send auth on AI_APP_READY
authGetter () => AuthPayload null Function to get current auth
Output Type Description
onReady void Iframe app is ready
onMessage BridgeMessage Any bridge message received
onDrop DragPayload Drag-drop completed
onError Error Bridge error occurred
onDisconnect void Heartbeat timeout
onReconnect void Connection re-established

GoGrabAiPanelComponent

Selector: gograb-ai-panel Role: High-level wrapper that builds the /panel URL with workspace-aware port and passes it to EmbeddableHostComponent.

Input Type Default Description
context string 'Dashboard' Feature context for AI suggestions
spaceId string '' Space ID for scoped operations
tier 'T1' / 'T2' / 'T3' 'T3' User tier (maps to userRole URL param)
theme 'dark' / 'light' 'dark' Theme override
enableDragDrop boolean true Enable drag-drop
authGetter () => AuthPayload null Auth function
aiBaseUrl string null Override auto-detected base URL

GoGrabObservatoryComponent

Selector: gograb-observatory Role: High-level wrapper that builds the /observatory URL.

Input Type Default Description
mode 'compact' / 'full' 'compact' Display mode
showControls boolean true Show control buttons
spaceId string '' Space ID
authGetter () => AuthPayload null Auth function
aiBaseUrl string null Override auto-detected base URL

AiPanelTabsComponent (Session 105.3)

Selector: ai-panel-tabs Library: @libs-ai/vitto-hub Role: Wraps AIPanelComponent and AgentObservatoryComponent with tab switching for native embedding.

Input Type Default Description
context string '' Pass-through to Studio
tenantId string '' Pass-through to Studio
spaceId string '' Pass-through to Studio
observatoryMode 'compact' / 'full' 'compact' Observatory display mode
observatoryControls boolean true Observatory control visibility
Output Type Description
close void Panel close requested
capabilityUsed AIPanelCapability AI capability was used
messageSent AIChatMessage Message sent to AI
outputDragged ActionableOutput Output dragged from panel
outputExecuted ActionableOutput Output executed in panel

Comparison Table

Aspect Direct (Native) Embeddable Bridge Chrome Extension
Component <ai-panel-tabs> <gograb-ai-panel> sidepanel.js iframe
Location Same Angular app Different Angular app Browser extension
Communication Angular I/O bindings PostMessage bridge PostMessage (vanilla JS)
Auth Shared DI tree Bridge AI_AUTH_INIT URL params (primary)
Port N/A (same app) deriveAiPort() Runtime port probing
Tab Switching AiPanelTabsComponent N/A (host decides) sidepanel.html tab bar
Drag-Drop Direct events Bridge AI_DRAG_* Not supported
Heartbeat N/A AI_PING/AI_PONG AI_PING/AI_PONG
Use Cases gograb-space gograb-admin, gograb-dev Chrome side panel

Troubleshooting

ERR_CONNECTION_REFUSED

Symptom: Iframe shows "localhost refused to connect"

Cause: The gograb-ai app is not running on the expected port.

Fix:

  1. Verify the app is running: yarn serve:gograb-ai
  2. Check the port in .env.local matches the workspace (e.g., GOGRAB_AI_PORT=6400 for gspace1)
  3. Never run npx nx serve gograb-ai directly -- use yarn serve:gograb-ai which reads .env.local via dev-serve.js

Blank Panel (No Content)

Symptom: Iframe loads but shows nothing

Causes:

Icons Show as Text

Symptom: Mode tabs show "chat", "image", "videocam" as plain text

Cause: Material Icons font not loaded in gograb-ai

Fix: Ensure index.html has:

<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">

Chrome PNA Preflight (Private Network Access)

Symptom: 404 Not Found on OPTIONS requests when embedding from extension

Cause: Chrome requires Access-Control-Allow-Private-Network: true header for local network access

Fix: The apps/gograb-ai/proxy.conf.js handles this with a /** catch-all that responds to OPTIONS with PNA headers.

WebSocket 400 Error on Backend

Symptom: Backend WebSocket connections fail with HTTP 400

Cause: --port flag was passed to a backend @nx/js:node executor. This sets the Node.js inspector port, not the app port, causing the inspector to intercept WebSocket upgrade requests.

Fix: Never pass --port to backend serve commands. Use yarn serve:backend-ai which reads the correct port from .env.local.

Observatory "Connected: No"

Symptom: Observatory panel shows "Connected: No" and "Error"

Cause: VittoMonitorService uses EventSource (SSE) which cannot send custom HTTP headers. If the backend requires Authorization, the SSE connection gets 401 Unauthorized.

Fix: Either configure the backend to accept cookie-based auth for SSE, or implement a token-in-URL scheme for the SSE endpoint.


File Reference

File Purpose
libs/libs-ux/embeddable-bridge/src/lib/components/embeddable-host.component.ts Host iframe container and adapter wiring
libs/libs-ux/embeddable-bridge/src/lib/components/gograb-ai-panel.component.ts Builds /panel URL with workspace port
libs/libs-ux/embeddable-bridge/src/lib/components/gograb-observatory.component.ts Builds /observatory URL with workspace port
libs/libs-ux/embeddable-bridge/src/lib/utils/workspace-ports.ts deriveAiPort() function
libs/libs-ux/embeddable-bridge/src/lib/models/bridge-events.model.ts Message types and payload interfaces
libs/libs-ux/embeddable-bridge/src/lib/services/host-adapter.service.ts Host-side bridge (send/receive/heartbeat)
libs/libs-ux/embeddable-bridge/src/lib/services/embedded-adapter.service.ts Embedded-side bridge (receive/reply/URL auth)
apps/gograb-ai/src/app/pages/extension-panel/extension-panel.component.ts /panel route, parses URL params
apps/gograb-ai/src/app/pages/observatory/observatory-page.component.ts /observatory route, parses URL params
apps/gograb-ai-extension/sidepanel/sidepanel.js Extension side panel, iframe URL building
apps/gograb-ai/proxy.conf.js PNA preflight handler for Chrome embedding
libs/libs-ai/vitto-hub/src/lib/components/ai-panel-tabs/ Studio/Observatory tab switcher (Session 105.3)