Skip to content

Embed Architecture ​

The Gnosari widget embedding system uses a three-layer architecture to provide complete isolation, security, and automatic updates for embedded chat interfaces.

Architecture Overview ​

Layer 1: Widget Script ​

The widget loader script handles integration with external websites.

File Location ​

/public/gnosari-chat-widget-advanced.js

Responsibilities ​

  1. iframe Creation

    • Dynamically creates iframe elements
    • Constructs embed URLs with query parameters
    • Injects iframe into page DOM
  2. UI Management

    • Renders floating button (bubble mode)
    • Handles positioning and sizing
    • Manages open/close animations
  3. Event System

    • Listens for postMessage from iframe
    • Triggers custom DOM events
    • Provides callback functions
  4. Public API

    • Exposes global GnosariChatAdvanced object
    • Methods: open(), close(), toggle(), sendMessage(), destroy()
    • State management

Initialization Flow ​

javascript
// 1. Script loads
window.gnosariConfig = { agentId: 'bot', apiUrl: 'https://...' };

// 2. Widget initializes
// - Reads config from window.gnosariConfig
// - Creates iframe element
// - Sets up event listeners
// - Renders UI (button or container)

// 3. Widget ready
window.dispatchEvent(new CustomEvent('gnosari-chat-ready'));

// 4. User opens chat
// - iframe becomes visible
// - Embed page loads inside iframe

Cross-Origin Communication ​

Widget script communicates with the iframe via postMessage:

Parent → Iframe:

javascript
iframe.contentWindow.postMessage({
  type: 'send-message',
  message: 'Hello!'
}, '*');

Iframe → Parent:

javascript
window.parent.postMessage({
  type: 'message-received',
  data: { ... },
  source: 'gnosari-chat-embed'
}, '*');

Layer 2: Embed Pages ​

Specialized Nuxt pages designed to be loaded in iframes.

Available Embed Routes ​

RoutePurposeExample URL
/embed/agent/[identifier]Agent chat/embed/agent/support-bot
/embed/team/[id]Team chat/embed/team/123
/embed/session/[id]Resume session/embed/session/sess_abc
/embed/l/[slug]Link chat/conversation/embed/l/vip-dinner

Agent Embed ​

File: /app/pages/embed/agent/[identifier].vue

Features:

  • Pre-fetches agent data for theme/config
  • Resolves agent chat_ui settings with theme defaults
  • Handles password-protected agents
  • Supports query param configuration overrides

Query Parameters:

ParameterTypeDescription
passwordstringSkip password prompt for protected agents
session_idstringResume existing chat session
themestringOverride theme: light, dark, auto
disableThemebooleanIgnore agent's chat theme
autoMessagestringSend message automatically on connect
showCloseButtonbooleanShow close button in chat
channelstringOrigin channel override for the created session. Defaults to WIDGET when absent or invalid

Example URL:

/embed/agent/support-bot?theme=dark&autoMessage=Hi%20there!

File: /app/pages/embed/l/[slug].vue

Features:

  • Resolves the Link's slug server-side via GET /resolve/l/{slug} (the sole visit_count increment site)
  • Presentation-aware: conversation renders ConversationSurface directly from the resolve payload (unchanged). chat performs a SECOND, isolated, non-fatal SSR fetch (GET /api/resolve?uri=) and applies applyAgentConfig() to pull the agent's full chat_ui (empty-state copy, suggested prompts, avatar, selector visibility) with the link's 3-tier effective theme -- parity with /embed/agent/[uri]. A failed agent fetch degrades to the minimal chat render, never the link error screen
  • Self-sufficient 410 (expired) / 404 (not found) / generic-error states, with retry
  • Supports host-blend query params for merging the canvas into the surrounding page

Query Parameters:

ParameterTypeDescription
themestringForce color mode: light or dark. Unset follows the visitor's OS preference
transparentboolean (1)See-through canvas -- merges into the host page's background. Requires an explicit theme matching the host's own color scheme, or Chromium opacifies the canvas anyway
fadeboolean (1)Soft gradient behind the composer band instead of a hard edge
bgstringExact canvas fill color as a hex value, no # (e.g. bg=0a0a0a) -- for hosts where transparent can't apply

Precedence when combined: transparent > bg > the derived theme surface.

Example URL:

/embed/l/vip-dinner?transparent=1&theme=dark&fade=1

Team Embed ​

File: /app/pages/embed/team/[id].vue

Similar to agent embed, but connects to a team instead.

Example URL:

/embed/team/123?password=secret

Session Embed ​

File: /app/pages/embed/session/[id].vue

Resume a specific chat session by ID.

Example URL:

/embed/session/sess_abc123

Embed Layout ​

File: /app/layouts/embed.vue

Minimal layout with no header, sidebar, or footer. Provides clean iframe experience.

Features:

  • No navigation chrome
  • Full-height layout (100dvh — dynamic viewport height so the input stays visible above mobile browser chrome)
  • Transparent background
  • No scroll (chat handles scrolling internally)

Configuration Resolution ​

Embed pages resolve configuration in this priority order:

  1. Agent chat_ui settings (highest priority)
  2. Chat theme defaults (if theme exists and not disabled)
  3. Query parameter overrides
  4. Component defaults (fallback)

Example:

javascript
// Agent has chat_ui.primaryColor = '#10b981'
// Theme has primaryColor = '#6366f1'
// URL has ?primaryColor=#8b5cf6

// Result: Agent chat_ui wins (#10b981)
// Unless disableTheme=true, then query param wins (#8b5cf6)

Layer 3: GnosariChat Component ​

The core chat component that provides full chat functionality.

File: /app/components/chat/GnosariChat.vue

Features ​

  • Authentication: Handles JWT tokens, password prompts
  • Message Streaming: Real-time message streaming via WebSocket
  • Session Management: Create, resume, and manage chat sessions
  • Team/Agent Selection: Switch between team members
  • File Upload: Image and document upload support
  • Message History: Scroll to load older messages
  • Typing Indicators: Shows when agent is typing
  • Error Handling: Graceful degradation on errors

Props Passed from Embed Page ​

PropTypeDescription
agent-idstringAgent identifier
agentobjectPre-fetched agent data
session-idstringSession to resume
passwordstringPassword for auth
skip-authbooleanSkip JWT auth (embeds use this)
show-close-buttonbooleanShow close button
heightstringComponent height (defaults to 100dvh)
featuresobjectFeature configuration
...resolvedPropsvariousTheme/config overrides

Events Emitted ​

EventDataDescription
ready{ agentIdentifier }Chat initialized
connected{ agentId, agentName }Connected to agent
session-created{ sessionId }New session created
session-loaded{ sessionId }Existing session loaded
message-sent{ message }User sent message
message-received{ messageId, role, content }Agent responded
error{ error }Error occurred
close-User clicked close button

Cross-Origin Security ​

Content Security Policy ​

Embed pages set strict CSP headers:

Content-Security-Policy:
  frame-ancestors 'self' https://*.yourdomain.com;
  script-src 'self' 'unsafe-inline';
  connect-src 'self' wss://*.yourdomain.com;

CORS Configuration ​

API endpoints allow cross-origin requests from configured domains:

javascript
// Backend CORS config
{
  allowedOrigins: ['*'], // or specific domains
  allowCredentials: true,
  allowedMethods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization']
}

postMessage Validation ​

Widget script validates all incoming postMessage events:

javascript
window.addEventListener('message', (event) => {
  // Validate source
  if (event.data.source !== 'gnosari-chat-embed') return;

  // Validate structure
  if (!event.data.type) return;

  // Process trusted message
  handleMessage(event.data);
});

Performance Considerations ​

Lazy Loading ​

The iframe is not created until needed:

javascript
// Bubble mode: iframe created on first button click
// Sidebar mode: iframe created when sidebar opens
// autoOpen: true: iframe created immediately

Resource Optimization ​

  • Code Splitting: Embed pages use separate Nuxt chunks
  • Caching: Widget script cached with long TTL
  • Compression: All assets served with gzip/brotli

Bundle Sizes ​

AssetSize (Compressed)
Widget script~50KB
Embed page (initial)~200KB
Chat component~150KB
Total~400KB

Load Performance ​

Typical load times:

  1. Widget script load: 100-200ms
  2. Iframe creation: 50-100ms
  3. Embed page load: 200-500ms
  4. Chat ready: 100-200ms
  5. Total to interactive: 500-1000ms

Debugging ​

Enable Debug Mode ​

javascript
window.gnosariConfig = {
  agentId: 'support-bot',
  debug: true  // Enables console logging
};

Debug Output ​

With debug enabled, you'll see:

🎨 Embed: Theme disabled, applying agent chat_ui settings only
📡 Embed: Chat ready
📡 Embed: Connected to agent: support-bot
📡 Embed: Session created: sess_abc123
📡 Embed: Message sent
📡 Embed: Message received

Inspecting postMessage Events ​

javascript
// Monitor all postMessage traffic
window.addEventListener('message', (event) => {
  console.log('postMessage received:', event.data);
});

Browser DevTools ​

To inspect the iframe:

  1. Open browser DevTools (F12)
  2. Go to Elements/Inspector tab
  3. Find the <iframe> element
  4. Right-click → Inspect
  5. DevTools context switches to iframe
  6. Now you can debug the embed page directly

Common Patterns ​

Dynamic Agent Switching ​

javascript
// Change agent dynamically
function switchAgent(agentId) {
  window.GnosariChatAdvanced.destroy();
  window.gnosariConfig.agentId = agentId;
  // Re-initialize with new config
  // (Implementation depends on widget script version)
}

Session Persistence ​

javascript
// Save session ID for later
window.addEventListener('gnosari-chat-session-created', (e) => {
  localStorage.setItem('chatSessionId', e.detail.sessionId);
});

// Resume session on next page load
window.gnosariConfig = {
  agentId: 'support-bot',
  sessionId: localStorage.getItem('chatSessionId')
};

Custom Event Tracking ​

javascript
window.addEventListener('gnosari-chat-open', () => {
  // Track in Google Analytics
  gtag('event', 'chat_opened', { agent: 'support-bot' });
});

window.addEventListener('gnosari-chat-new-message', () => {
  // Track message received
  gtag('event', 'chat_message_received');
});

Conditional Widget Loading ​

javascript
// Only load widget for specific pages or conditions
if (window.location.pathname.includes('/pricing')) {
  window.gnosariConfig = {
    agentId: 'sales-bot',
    autoOpen: true
  };

  const script = document.createElement('script');
  script.src = 'https://your-domain.com/gnosari-chat-widget-advanced.js';
  document.body.appendChild(script);
}

Migration from Basic to Advanced ​

If you're currently using the basic widget (gnosari-chat-widget.js):

Basic Widget:

html
<script src=".../gnosari-chat-widget.js"></script>
  • Opens chat in new window/popup
  • Minimal customization
  • < 10KB bundle size

Advanced Widget:

html
<script src=".../gnosari-chat-widget-advanced.js"></script>
  • Embedded iframe chat
  • Full customization
  • ~50KB bundle size
  • Better UX on mobile

Migration steps:

  1. Change script URL from gnosari-chat-widget.js to gnosari-chat-widget-advanced.js
  2. Update window.gnosariConfig with new options (see Configuration Options)
  3. Test on your site
  4. Update any event listeners (event names may differ)