Getting StartedArchitecture OverviewExtension System Architecture

Extension System Architecture

Deep dive into how extensions are sandboxed, communicate with haex-vault, and integrate with the sync system.

Extension Overview

Extensions are sandboxed web applications that run within haex-vault. They can access vault data through the SDK while being isolated from the host system and each other.

Sandboxed Isolation

Extensions run in isolated environments with explicit permissions for each action.

Unified SDK

The haex-vault-sdk provides a consistent API regardless of the display mode.

Own Database Tables

Extensions can create and manage their own tables with automatic CRDT sync.

Automatic Sync

Extension data is automatically synced across devices using the same CRDT system.

Display Modes

Extensions can run in two modes depending on the platform. The SDK abstracts away the differences.

Aspect
iframe
WebView
PlatformDesktopMobile (Android/iOS)
IsolationBrowser SandboxProcess Isolation
CommunicationpostMessage APITauri Events
DebuggingDevToolsNative Debugger
PerformanceLightweightHeavier

Configure in Manifest

// manifest.json
{
  "displayMode": "auto",  // "auto" | "iframe" | "webview"
  // auto: iframe on desktop, webview on mobile
}

Extension Lifecycle

Extensions go through a defined lifecycle from installation to runtime.

┌─────────────────────────────────────────────────────────────┐
│                    Extension Installation                    │
└──────────────────────────┬──────────────────────────────────┘
                           │
                           ▼
┌─────────────────────────────────────────────────────────────┐
│   1. preview_extension(fileBytes)                            │
│      - Extract ZIP to temp dir                              │
│      - Validate manifest.json                               │
│      - Verify public_key & signature                        │
│      - Return ExtensionPreview for user review              │
└──────────────────────────┬──────────────────────────────────┘
                           │
                           ▼
┌─────────────────────────────────────────────────────────────┐
│   2. User reviews & grants permissions                       │
└──────────────────────────┬──────────────────────────────────┘
                           │
                           ▼
┌─────────────────────────────────────────────────────────────┐
│   3. install_extension_with_permissions()                    │
│      - INSERT into haex_extensions (CRDT)                   │
│      - INSERT into haex_extension_permissions               │
│      - Extract ZIP to app_data_dir/extensions/              │
│      - Load into ExtensionManager (memory)                  │
│      - Run database migrations (CREATE TABLE with CRDT)     │
└──────────────────────────┬──────────────────────────────────┘
                           │
                           ▼
┌─────────────────────────────────────────────────────────────┐
│   4. Extension ready                                         │
│      - Tables synced via CRDT                               │
│      - User can open extension                              │
└─────────────────────────────────────────────────────────────┘

Cross-Device Installation:When you install an extension, the metadata is synced to other devices. However, the extension files must be downloaded separately on each device from the marketplace.

Permission System

Extensions must declare permissions in their manifest. Users grant or deny these permissions during installation.

// Permission Model
{
  resourceType: "db" | "fs" | "web" | "shell",
  action: "read" | "write" | "create" | "delete" | "alterDrop",
  target: "haex_passwords" | "/home/user/*" | "https://api.example.com/*",
  constraints: { maxFileSize: "10MB" },  // optional
  status: "granted" | "denied" | "ask"
}

Permission Types

Database

  • read - SELECT only
  • readWrite - SELECT + INSERT/UPDATE
  • create - CREATE TABLE
  • delete - DELETE rows
  • alterDrop - ALTER/DROP

Filesystem

  • read - Read files
  • readWrite - Read + Write files
  • delete - Delete files

Permission Enforcement

// Rust - Permission Check Flow
fn execute_sql(extension_id: &str, sql: &str) -> Result<...> {
    let permission = permission_manager.check_permission(
        extension_id,
        ResourceType::Database,
        Action::ReadWrite,
        sql_table_name,
        None
    )?;

    match permission.status {
        Status::Granted => execute(sql),
        Status::Denied => Err(PermissionDenied),
        Status::Ask => {
            // Show permission prompt in UI
            // Cache decision for future
        }
    }
}

Extension Communication

Extensions communicate with the host through different mechanisms depending on the display mode.

iframe Communication (Desktop)

// Extension → Host (via postMessage)
window.parent.postMessage({
  type: 'haextension:invoke',
  id: 'request-123',
  method: 'database',
  action: 'select',
  args: ['haex_passwords', { where: { id: 'abc' } }]
}, '*')

// Host → Extension (via postMessage)
window.frames[extensionId].postMessage({
  type: 'haextension:response',
  id: 'request-123',
  result: [{ id: 'abc', title: 'Gmail' }]
}, '*')

WebView Communication (Mobile)

// WebView extensions use Tauri events directly
import { listen, emit } from '@tauri-apps/api/event'

// Extension → Host
await emit('haextension:invoke', {
  method: 'database',
  action: 'select',
  args: ['haex_passwords']
})

// Host → Extension
await listen('haextension:response', (event) => {
  console.log(event.payload)
})

Development Mode

During development, extensions can be loaded from a local dev server with hot reload support.

Hot Reload

Changes to your extension code are reflected immediately without reinstalling.

Local-Only Data

Dev extension tables are NOT synced to other devices. They use CREATE TABLE IF NOT EXISTS for idempotency.

No CRDT Triggers:Dev extension tables don't get CRDT triggers, so changes are local-only. Production extensions get full sync support.

Receiving Sync Updates

Extensions can listen for sync events to reload their data when changes arrive from other devices.

// In your extension (using vault-sdk)
import { HAEXTENSION_EVENTS } from '@haex-space/vault-sdk'

vault.on(HAEXTENSION_EVENTS.SYNC_TABLES_UPDATED, async (event) => {
  const tables = event.data?.tables || []

  // Check if our tables are affected
  const ourTablePrefix = vault.getTableName('')
  const hasOurTables = tables.some(t => t.startsWith(ourTablePrefix))

  if (hasOurTables) {
    // Reload data from database
    await reloadDataAsync()
  }
})

Automatic Table Prefix:Extension tables are automatically prefixed with the extension's public key and name, so you only receive events for your own tables.