The SDK & bridge
The SDK & postMessage bridge
How your app talks to the workspace host — the postMessage protocol, scoped tokens, and file access via the Verentis SDK.
Your app runs in a sandboxed iframe, isolated from the workspace host. The Verentis SDK
(@verentis/sdk) connects the two over a postMessage protocol so your app can receive launch
context and call platform APIs with a short-lived token — without ever handling raw credentials.
The bridge in one picture
┌─────────────────────────────┐ postMessage ┌──────────────────────┐
│ Workspace host (parent) │ ◄────────────────────────► │ Your app (iframe) │
│ • injects scoped tokens │ init / events │ • @verentis/sdk │
│ • owns workspace routing │ │ • view / edit UI │
└─────────────────────────────┘ └──────────────────────┘
The host is the trusted broker. It holds the user session, mints short-lived tokens, and validates each request against the permissions the workspace consented to for your app.
Initialising the SDK
Connect to the host when your app loads:
import { createVerentisClient } from '@verentis/sdk'
const client = createVerentisClient()
await client.whenReady()
const { surface, workspace, entryPoint } = client.context
Tokens are injected, not stored
You never embed a client secret or API key in an iframe app. The host injects and refreshes a short-lived token containing the manifest permissions granted to the app. SDK modules use that token automatically.
File and workspace launch context
The context is a discriminated union:
if (client.context.surface === 'file') {
const file = client.context.file
const content = await client.files.readContent(file.path)
} else {
// A spec.launch app opened without a selected file.
// client.context.file is unavailable on this surface.
renderWorkspaceHome(client.context.workspace)
}
| Context property | File launch | spec.launch workspace launch |
|---|---|---|
surface | 'file' | 'workspace' |
workspace | Present | Present |
file / hasFile | File metadata / true | Throws if read / false |
entryPoint | Matched file-handler entry point | Configured launch entry point (or default) |
apiUrl | Present | Present |
Older hosts sent file context without surface; the SDK treats that legacy shape as file.
Two meanings of standalone
Manifest spec.launch still runs inside the workspace iframe and receives
surface: 'workspace'. SDK “standalone mode” means running outside any iframe with explicit
apiUrl, credentials and workspaceId; it is a separate local-development mode.
Reading and writing files
Use VFS paths with the SDK:
const content = await client.files.readContent('/reports/q1.json')
await client.files.write('/reports/q1.json', {
content: updatedJson,
contentType: 'application/json',
})
These calls go through the platform file/node APIs with the injected token. Declare the corresponding permissions in the manifest; see Working with files & nodes.
WOPI document sessions
For an approved file-bound editor, the same bridge exposes these methods:
| Method | Result and responsibility |
|---|---|
requestWopiLaunch() | WopiLaunch: action URL, operator origin, WOPISrc, document-scoped token, absolute expiry and document state. |
requestWopiSave(generation) | WopiSaveCheckpoint: safe nonnegative integer generation, correlation and optional revision. Requests a checkpoint, not a document upload. |
getWopiStatus() | WopiSaveStatus: state, revision, read-only flag, receipt and optional continuationAvailable. |
requestWopiContinuation() | WopiContinuation: rebind to the target of a completed server-owned derived operation. No new access token in this response. |
closeWopiSession() | Release session participation after the wrapper has handled pending edits. |
These methods require an initialized workspace bridge. The host binds requests
to the current iframe session and installed app; callers cannot choose arbitrary
document authority. WOPI-only wrappers do not request ordinary backend credentials
or use WOPI tokens as the bearer token for client.files calls.
await client.whenReady()
if (!client.bridge || client.context.surface !== 'file' ||
!client.context.installation?.wopiEnabled) {
throw new Error('This is not an approved WOPI launch.')
}
const launch = await client.bridge.requestWopiLaunch()
// Pass launch only into the wrapper's validated CODE framing flow.
Track dirty generations, send CODE the checkpoint correlation and wait for a matching durable receipt before reporting a save. Neither the checkpoint request nor CODE's save-response message alone confirms persistence. See WOPI integration for the framing, save, Save As and failure-handling sequence.
Host navigation
Ask the host to navigate instead of assigning the parent window:
client.bridge?.navigate('/reports/q1.json')
client.bridge?.navigate('/reports/q1.json', { newTab: true })
client.bridge?.navigate('https://docs.example.com', { newTab: true })
A VFS path is converted to /files/** by the host. /files/** is also accepted, but old root-level
browser URLs are not redirected. Absolute HTTP(S) targets require the
navigation:external capability; other schemes and protocol-relative URLs are rejected.
Hosted application actions
The workspace content header is shared by file browsing, application views, and execution views. For file-bound applications it displays the file name first, with application identity beneath it. Register document-level actions through SDK 0.2 rather than rendering a second Save/Copy toolbar:
await client.whenReady()
const descriptor = {
id: 'document.save',
label: 'Save',
icon: 'lucide:save',
scopes: ['node.file.read', 'node.node.create', 'node.node.update', 'node.journal.create'],
enabled: false,
}
const action = client.bridge!.registerAction(descriptor, async () => {
await saveDocument()
})
action.update({ ...descriptor, enabled: true })
// When the owning view is disposed:
action.dispose()
Handlers remain inside your iframe. The host receives only typed descriptors and sends an invocation
back to the SDK, which awaits the handler and returns its outcome. Throw failures instead of catching
them and returning success. Scope requirements must fit the delegated token; scopes: [] explicitly
identifies client-only operations. Supported presentation fields are icon (a Lucide identifier),
variant (default, outline, destructive), enabled, visible, busy, and disabledReason.
Use kind: 'clipboard' and return { clipboardText: source } for a host-owned clipboard operation.
Return { navigate: { path: '/target' } } when a completed mutation should navigate, so navigation
does not destroy the iframe before its result reaches the host.
Actions are bound to the current iframe session and cleared on reload/navigation. Timeouts mean the result is unknown; mutations are not retried automatically. At most 16 actions can be registered. There is no legacy toolbar fallback: coordinate the SDK, host, and app releases. Fullscreen entries hide the host header only when they have no registered actions.
Keep specialized controls such as zoom, preview/source modes, and contextual form buttons inside your application. Platform Run is not an inferred application capability: users open Source to reach the existing execution view, and must save unsaved editor changes before that transition.
Responsive iframe surfaces
The workspace uses a persistent sidebar on large screens and a navigation drawer on smaller screens. Notifications, the inspector and the assistant open as overlay panels. Your iframe therefore changes size without a page reload.
Build against the iframe's available width rather than 100vw, use fluid breakpoints, and test both
file and workspace launch surfaces. client.bridge?.resize(height) is available for
intrinsic-height content; a full-height application should normally fill the host-provided container.
Standalone development
While iterating outside the platform, create the client with an API URL, API key or bearer token, and
workspace ID. whenReady() resolves immediately and bridge is null:
const client = createVerentisClient({
apiUrl: 'https://api.localtest.me:6500',
auth: { apiKey: 'vrt_...' },
workspaceId: 'workspace-uuid',
})
Language SDKs for engines
Execution engines use the same pattern with a language-appropriate SDK (e.g. Python) inside the runtime. The shape of the API — context, scoped token, file read/write — mirrors the TypeScript SDK. See The execution model.
Next
How your app's OAuth client and scopes are approved on install.