@tavora/sdk
v0.3.4
Published
Typed client for Tavora modules: talks to the Tavora Desktop host over its bridge.
Readme
@tavora/sdk
Typed client for Tavora modules. A module is a web page that runs in a sandboxed panel inside Tavora Desktop; this package is how it talks to the host.
Status: bridge protocol v1, still before 1.0: a minor version can change things, and the changelog says what and which Tavora a version needs.
Start a new module with npm create @tavora/module (or pnpm create @tavora/module), or add the SDK to your own project with npm install @tavora/sdk.
import { connect, TavoraError } from "@tavora/sdk";
const tavora = await connect();
const workspace = await tavora.workspace.getCurrent(); // { id: "poe2", displayName: "Path of Exile 2", kind: "game" }
// Small settings, private to this module in this workspace.
const count = (await tavora.storage.get<number>("count")) ?? 0;
await tavora.storage.set("count", count + 1);
// Documents are shared by all panels of this module in this workspace.
const plan = await tavora.documents.get<{ steps: string[] }>("plan");
try {
await tavora.documents.put({ id: "plan", title: "Plan", data: { steps: ["Act 1"] }, ifRevision: plan?.revision ?? 0 });
} catch (error) {
if (error instanceof TavoraError && error.code === "CONFLICT") {
// Another panel saved first: reload instead of overwriting.
}
}
tavora.documents.onChange(({ id }) => console.log(`${id} changed in another panel`));
// State for this one panel (open tab, scroll position). Removed when the panel closes.
await tavora.panel.setState({ tab: "gear" });
tavora.workspace.onResize(({ width, height }) => {});
tavora.ui.onThemeChange((theme) => {});
tavora.lifecycle.onSuspend(() => {}); // hidden: pause timers and animations
tavora.lifecycle.onResume(() => {});connect() can be called any number of times and always returns the same connection. It rejects with a TavoraError when the page runs outside Tavora (HOST_UNAVAILABLE) or the host speaks another protocol version (INCOMPATIBLE).
Every failed call rejects with a TavoraError. Check error.code, not the message. The codes, limits and the wire format are in the bridge protocol reference; the manifest every module needs is in the manifest reference.
Values you store must be JSON. Type parameters such as get<number>() are not checked at runtime.
More from the host
| API | What it does |
| --- | --- |
| tavora.workspace.getCurrent() | The workspace the panel is in: { id, displayName, kind }, with kind "game" (a game workspace; the id is the game's) or "personal". tavora.game.getCurrent() still answers but is deprecated. |
| tavora.storage.app | The same small settings, shared by every workspace: for choices the user expects to stay the same from workspace to workspace. |
| tavora.ui.getLanguage(), onLanguageChange | The language of Tavora's own texts ("en", "de"). Show your module in it, or in English when you have no translation. |
| tavora.blobs | Pictures too big for a document: up to 50 MB each and 500 MB per module and workspace. put a file, show it with url(id). See Files. |
| tavora.clipboard.writeImage(png) | Puts a PNG on the system clipboard. Call it from the click or key press that asked for it. See Clipboard. |
| tavora.pictures.save(png, name) | Saves a PNG in the user's Pictures\Tavora folder and resolves with the file name. Call it from the click or key press that asked for it. See Saving pictures. |
| tavora.links.open(url) | Opens a web address in the workspace's Browser panel, from a click or key press. |
| tavora.network.fetch(url, init) | A web request to a host listed in permissions.network, once the user granted it. See Network. |
| tavora.network.readPage(url) | Capture a public page in an isolated temporary browser, using the same grants. No user sign-ins or module scripts; Tavora 0.19.3+. |
| tavora.connectors.aiUsage | The usage of the user's Claude and Codex accounts, with the ai-usage connector. |
| tavora.connectors.systemInfo | The PC's hardware and how busy it is, with the system-info connector: info(), watch(options), onSnapshot, onDrivesChange, and for the connection connectionLog(), clearConnectionLog(), connectionSettings(), setConnectionSettings(settings), onConnectionChange. |
| tavora.connectors.media | What plays on the PC, with the media connector: watch(), unwatch(), onChange, control(session, action). |
| tavora.connectors.discord | The Discord app on this PC through Tavora's Discord sign-in, with the discord connector: voice channels, mute and deafen, voice events. |
What needs the user's permission (permissions.network and permissions.connectors in the manifest) works only once the user has granted it: they are asked when they add the module and can take it back in Settings > Connections. Until then the call fails with PERMISSION_DENIED; tavora.host.capabilities lists what was granted when the module connected. See Capabilities and Connectors.
Examples
The same small module built three ways: vanilla TypeScript, React and Vue.
Building a module
- Bundle everything. A module's page has no network access, so no CDN scripts, web fonts or remote images. To reach a web API, list its host in
permissions.networkand usetavora.network.fetchonce the user has granted it. - Use relative asset paths (Vite:
base: "./"). The page is served fromtavora-module://<module-id>/. - Point the manifest's
entryat the built HTML file.
Details over neighbouring panels
Requires Tavora 0.18.18. tavora.ui.tooltip.show accepts TooltipRequest, a bounded
plain-data description, never HTML or CSS. Use a new id for each hover session,
move while the pointer moves, and hide on pointer leave. A click may call
show with pinned: true; hide then leaves it open. Call close before changing
the source data, and use onClose to clear your local pin state. Escape and the
host close button dismiss the current tooltip from anywhere in Tavora.
await tavora.ui.tooltip.show({
id: 'item:42', at: { x: event.clientX, y: event.clientY },
content: { title: 'Example spear', tone: 'rare',
sections: [{ rows: [{ text: 'Adds 10 to 22 Physical Damage', badges: ['[P5]'] }] }] },
pinned: false,
});