External Bridge
Receive requests from browser extensions, CLI tools, and other external clients.
Overview
The External Bridge allows external clients to communicate with your HaexVault extension via a secure WebSocket connection. This enables powerful integrations like browser extensions for password autofill, CLI tools for automation, or server-side applications.
Autofill passwords, sync bookmarks, or access secure notes directly from your browser.
Automate workflows, manage secrets, or integrate with scripts and CI/CD pipelines.
Build backend services that securely access your extension's data.
External clients must be authorized by the user before they can send requests. All communication is encrypted end-to-end.
How it Works
The External Bridge uses a WebSocket-based protocol with public key authentication:
- External client connects to HaexVault's WebSocket server
- Client sends handshake with public key, name, and requested extensions
- HaexVault shows authorization dialog - user can Allow Once, Allow Always, or Block
- Authorized clients can send requests targeting a specific extension
- If the extension is not running, HaexVault auto-starts it (minimized) to handle the request
- Extension processes the request and sends response back to the client
External Client HaexVault Extension
(Browser Extension) (Desktop App) (Plugin)
| | |
|-- WebSocket Connect --------->| |
| | |
|<-- Connection Accepted -------| |
| | |
|-- Handshake (publicKey, ------| ← Shows Authorization |
| clientName, extensions) | Dialog to User |
| | |
|<-- Paired (authorized) -------| |
| | |
|-- Request (action, payload) ->| ← Auto-starts Extension |
| | if not running (minimized) |
| | |
| |-- Event: external:request -->|
| | |
| |<-- Response (data) ----------|
| | |
|<-- Response (data) -----------| |
| | |
Handling Requests
Your extension receives external requests through events. You can either listen to all requests or register handlers for specific actions.
Event Listener (handle all requests)
import { EXTERNAL_EVENTS } from '@haex-space/vault-sdk'
import type { ExternalRequest, ExternalResponse } from '@haex-space/vault-sdk'
// Define your extension's action constants
export const ACTIONS = {
GET_CREDENTIALS: 'getCredentials',
SAVE_CREDENTIAL: 'saveCredential',
DELETE_CREDENTIAL: 'deleteCredential',
} as const
const { client } = useHaexVaultSdk()
// Listen for external requests
client.on(EXTERNAL_EVENTS.REQUEST, async (event) => {
const request: ExternalRequest = event.data
const { requestId, publicKey, action, payload } = request
console.log(`External request from ${publicKey}: ${action}`)
try {
let responseData: unknown
switch (action) {
case ACTIONS.GET_CREDENTIALS:
responseData = await getCredentialsForUrl(payload.url as string)
break
case ACTIONS.SAVE_CREDENTIAL:
await saveCredential(payload)
responseData = { saved: true }
break
default:
throw new Error(`Unknown action: ${action}`)
}
// Send success response
await client.respondToExternalRequest(requestId, {
requestId,
success: true,
data: responseData
})
} catch (error) {
// Send error response
await client.respondToExternalRequest(requestId, {
requestId,
success: false,
error: error instanceof Error ? error.message : 'Unknown error'
})
}
})
Register Handler (per action)
import type { ExternalRequest, ExternalResponse } from '@haex-space/vault-sdk'
// Define your extension's action constants
export const ACTIONS = {
GET_CREDENTIALS: 'getCredentials',
SAVE_CREDENTIAL: 'saveCredential',
} as const
const { client } = useHaexVaultSdk()
// Register handlers for specific actions (cleaner alternative to event listener)
client.onExternalRequest(ACTIONS.GET_CREDENTIALS, async (request: ExternalRequest) => {
const { url } = request.payload as { url: string }
const credentials = await getCredentialsForUrl(url)
return {
requestId: request.requestId,
success: true,
data: credentials
}
})
client.onExternalRequest(ACTIONS.SAVE_CREDENTIAL, async (request: ExternalRequest) => {
await saveCredential(request.payload)
return {
requestId: request.requestId,
success: true,
data: { saved: true }
}
})
Connection States
External clients go through several connection states. These are primarily used by client implementations (browser extensions), but understanding them helps when debugging.
import {
ExternalConnectionState,
ExternalConnectionErrorCode,
isExternalClientConnected,
canExternalClientSendRequests
} from '@haex-space/vault-sdk'
// Connection states (used by external clients like browser extensions)
ExternalConnectionState.DISCONNECTED // Not connected to HaexVault
ExternalConnectionState.CONNECTING // Attempting to connect
ExternalConnectionState.CONNECTED // WebSocket connected, not authorized
ExternalConnectionState.PENDING_APPROVAL // Waiting for user approval
ExternalConnectionState.PAIRED // Authorized and ready
// Error codes for i18n
ExternalConnectionErrorCode.NONE // No error
ExternalConnectionErrorCode.CLIENT_NOT_AUTHORIZED // Not authorized
ExternalConnectionErrorCode.CLIENT_BLOCKED // User blocked this client
ExternalConnectionErrorCode.CONNECTION_FAILED // HaexVault not running
ExternalConnectionErrorCode.CONNECTION_TIMEOUT // Timeout
ExternalConnectionErrorCode.CONNECTION_CLOSED // Unexpected disconnect
ExternalConnectionErrorCode.DECRYPTION_FAILED // Invalid key
ExternalConnectionErrorCode.INVALID_MESSAGE // Corrupted message
// Helper functions
isExternalClientConnected(state) // true if connected/pending/paired
canExternalClientSendRequests(state) // true only if PAIRED
Type Definitions
TypeScript interfaces for External Bridge communication:
// External request received by your extension
interface ExternalRequest {
requestId: string // Unique ID for response correlation
publicKey: string // Client's public key (identifier)
action: string // Action to perform (your constants)
payload: Record<string, unknown> // Request data
}
// Response sent back to external client
interface ExternalResponse {
requestId: string // Must match request
success: boolean // Whether request succeeded
data?: unknown // Response data (if success)
error?: string // Error message (if failed)
}
// Authorized client (stored in database)
interface AuthorizedClient {
id: string
clientId: string // Public key fingerprint
clientName: string // Human-readable name
publicKey: string // Full public key (base64)
extensionId: string // Which extension this client can access
authorizedAt: string | null // ISO 8601 timestamp
lastSeen: string | null // Last connection time
}
// Blocked client (stored in database)
interface BlockedClient {
id: string
clientId: string
clientName: string
publicKey: string
blockedAt: string | null
}
// Pending authorization (in-memory, shown in approval dialog)
interface PendingAuthorization {
clientId: string
clientName: string
publicKey: string
requestedExtensions: RequestedExtension[]
}
interface RequestedExtension {
name: string // e.g., "haex-pass"
extensionPublicKey: string // Extension's manifest public key
}