Skip to content

Latest commit

 

History

History
92 lines (65 loc) · 3.71 KB

File metadata and controls

92 lines (65 loc) · 3.71 KB

Host API reference

hermes-js-plugin-builder/host exports everything a web UI needs to embed and run plugins built by this toolkit.

import { PluginHost } from 'hermes-js-plugin-builder/host';
// or: import { PluginHost } from './src/host/index.js';

new PluginHost(options?)

Option Type Description
storage { get(key), set(key, v), delete(key) } Storage adapter backing ctx.storage. Default: in-memory. Wrap localStorage for persistence.
notify (n: {pluginId, message, level}) => void Sink for ctx.notifications.show. Default: console.log.
onPanel (panel: {pluginId, id, title, location}) => void Called when a plugin registers a panel; the embedder decides where/how to mount.
connect (manifest) => Promise<{endpoint, dispose}> Custom sandbox transport. endpoint = { postMessage(msg), onMessage(cb) -> unsubscribe }. Used for tests and non-DOM embeddings. Default: sandboxed iframe (browser) or Web Worker.
timeoutMs number RPC and boot timeout. Default 10000.

Methods

await host.load(source, manifest)

Load a plugin. source is a bundle URL/path (fetched as text) or a code string or { url } / { code }. manifest is the parsed plugin.json.

  • Validates the manifest first; rejects with protocol-error listing violations.
  • Creates the sandbox, runs the hp:hello → hp:init → hp:ready handshake, and resolves { id, manifest, state: "active" }.
  • Guest→host API calls are permission-gated: a plugin calling a namespace it didn't declare gets a permission-denied RPC error.

await host.unload(id)

Sends hp:unload, waits for the guest's deactivate() ack (bounded by timeoutMs), removes its commands/panels/subscriptions, and tears down the sandbox. Returns false if the plugin wasn't loaded.

host.list()

[{ id, manifest, state }] for every loaded plugin.

host.get(id)

The internal plugin record (includes peer — treat as opaque).

await host.executeCommand(pluginId, commandId, args?)

Host → guest commands.invoke: runs the handler the plugin registered for commandId and resolves its return value.

await host.renderPanel(pluginId, panelId, props?)

Host → guest panels.render: runs the panel's render(props) and resolves the HTML string it returned. Mounting that HTML is the embedder's job.

host.api

The host-side API surface (also usable standalone):

  • host.api.onEvent(topic, cb) — subscribe to plugin-emitted events.
  • host.api.emitEvent(topic, payload) — publish to host + subscribed plugins.
  • host.api.listCommands(pluginId?), host.api.listPanels(pluginId?) — enumerate live contributions.

Sandboxing model

  • Browser: an invisible <iframe sandbox="allow-scripts" srcdoc=...> containing the guest bootstrap. allow-scripts without allow-same-origin gives the guest an opaque origin — it can run JS but cannot touch the parent DOM, cookies, or storage. The plugin bundle is delivered as source text over postMessage and imported via a Blob URL inside the sandbox.
  • Worker fallback: when no DOM exists, the guest bootstrap runs in a Worker created from a Blob URL.
  • No eval on the host side for plugin code — everything crosses the sandbox boundary as data.

Permission enforcement is host-side: the guest can attempt any call, but the host dispatcher checks the declared manifest.permissions before executing, so a tampered guest cannot escalate.

Demo

python3 -m http.server  # from the repo root
# open http://localhost:8000/examples/host-demo/

examples/host-demo/index.html loads examples/hello-panel, mounts its panel, and wires buttons for its commands and the event bus.