Addon API
Everything on the gryt object a client plugin runs against, generated from the source
This is the reference. Addons is the page that teaches the shape, and plugin pairs covers the server half.
A plugin runs in a worker of its own, and gryt is a global in it. There is no
window.gryt and no addon id on any call — Gryt already knows which plugin is
asking.
What is not there
A worker has no page, and the rest is taken away before your module is imported:
window // a worker has none
document
localStorage
indexedDB // deleted off the prototype chain, not just off globalThis
caches
Worker // no nesting out of itYour network is not. Whatever you are granted, you can send anywhere, and that is the part worth telling people who install your plugin.
Capabilities
Declared in the manifest and agreed to per addon. Both, or the call rejects and says which is missing.
| Capability | What the person reads |
|---|---|
status | Set what you are doing, on every server you are on |
messaging | Exchange its own messages with the servers you are on |
display | Show its own panel beside the member list |
processes | See which of your listed programs are running |
The manifest
manifest.json, in your addon's folder.
| Field | Type | What it is |
|---|---|---|
id | string | |
name | string | |
version | string | |
type | "plugin" | "theme" | |
description? | string | |
author? | string | |
banner? | string | |
styles? | string[] | Theme-only: CSS files to inject |
main? | string | Plugin-only: JS entry point |
requiresReloadOnDisable? | boolean | Plugin-only: if true, disabling the addon reloads the client |
capabilities? | string[] | Plugin-only: what it says it needs to do (GRYT-928). Declared here and agreed to per addon before window.gryt will answer. Not a sandbox — see capabilities.ts for what this does and does not buy. A plugin runs in the app's own page and could go around it. Unknown names are dropped rather than refused, so a manifest written against a newer Gryt still loads on an older one. |
repository? | string | Where the addon is published, as owner/repo on GitHub. Optional. An addon without it never reports an update, which is the right default for one somebody wrote for themselves and dropped in the folder. Not free-form: this string decides what gets fetched, so a manifest whose repository is anything other than two plain path segments is rejected when it is read rather than when it is used. |
gryt
version
version: stringtheme
theme: ThemeInfoon()
on(event: "themeChange" | "cleanup", handler: (payload: ThemeInfo | undefined) => void): () => voidHear about something.
themeChange when the app's appearance or accent changes, and cleanup
when this plugin is being turned off — which is the only chance it gets to
clear a status or stop a timer. Whatever it does not finish quickly enough
happens anyway: the worker is terminated shortly after, from outside.
setActivity()
setActivity(activity: string): Promise<unknown>Needs status.
Say what the person running this is doing.
An empty string clears it. Rejects if the capability was not granted, rather than resolving quietly — a status that never appears is harder to work out than an error.
gryt.messaging
send()
send(topic: string, data: unknown, host?: string): Promise<unknown>Needs messaging.
Send to the copy of this plugin on the server.
on()
on(topic: string, handler: (message: unknown) => void): () => voidHear from it. Needs messaging.
servers()
servers(): Promise<string[]>Needs messaging.
Which servers run the other half. A round trip, so a promise.
gryt.processes
running()
running(): Promise<string[]>Needs processes.
Which of the programs the person listed are running.
Their list, not their machine. Somebody who has written down two games gets an answer about those two games and nothing about the other two hundred things they have open — which is what makes the capability's wording true rather than a euphemism.
Empty in a browser, and empty on the desktop until somebody lists something. Both look the same from here on purpose: a plugin should degrade rather than tell people to go and configure Gryt.
on()
on(handler: (running: string[]) => void): () => voidHear when that answer changes. Needs processes.
gryt.ui
panel()
panel(panel: { title: string; rows: { label: string; value?: string }[] }): Promise<unknown>Needs display.
Draw a panel beside the member list.
A title and rows of text, and nothing else — no markup, no colours, no node handed over. The app renders it with its own components, which is what lets a plugin be seen at all from inside a worker with no DOM.
Calling it again replaces what is there. There is one panel per plugin, so a plugin does not have to track handles or clean up more than one.
clear()
clear(): Promise<unknown>Needs display.
Take it down.
gryt.log
info()
info(message: string)warn()
warn(message: string)error()
error(message: string)