Gryt

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 it

Your 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.

CapabilityWhat the person reads
statusSet what you are doing, on every server you are on
messagingExchange its own messages with the servers you are on
displayShow its own panel beside the member list
processesSee which of your listed programs are running

The manifest

manifest.json, in your addon's folder.

FieldTypeWhat it is
idstring
namestring
versionstring
type"plugin" | "theme"
description?string
author?string
banner?string
styles?string[]Theme-only: CSS files to inject
main?stringPlugin-only: JS entry point
requiresReloadOnDisable?booleanPlugin-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?stringWhere 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: string

theme

theme: ThemeInfo

on()

on(event: "themeChange" | "cleanup", handler: (payload: ThemeInfo | undefined) => void): () => void

Hear 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): () => void

Hear 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): () => void

Hear 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)

On this page