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 |
|---|---|---|
| Platform | Desktop | Mobile (Android/iOS) |
| Isolation | Browser Sandbox | Process Isolation |
| Communication | postMessage API | Tauri Events |
| Debugging | DevTools | Native Debugger |
| Performance | Lightweight | Heavier |
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 onlyreadWrite- SELECT + INSERT/UPDATEcreate- CREATE TABLEdelete- DELETE rowsalterDrop- ALTER/DROP
Filesystem
read- Read filesreadWrite- Read + Write filesdelete- 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.