Addons
Themes and plugins, what a plugin can ask for, and what that does and doesn't protect you from
An addon is a folder Gryt reads at startup. A theme is CSS. A plugin is JavaScript.
Open Settings, then Addons, then Open folder to see where they go. Drop a folder in and Gryt picks it up without a restart.
Every folder needs a manifest.json:
{
"id": "nowplaying",
"name": "Now playing",
"version": "1.0.0",
"type": "plugin",
"description": "Puts the track you're listening to under your name",
"author": "you",
"main": "index.js"
}A theme uses "type": "theme" and a styles array instead of main.
What a plugin can do
Your plugin gets a gryt global. It holds the app version, the current theme,
an event for when the theme changes, and one thing a plugin can change:
await gryt.setActivity("🎧 Bohemian Rhapsody — Queen");That's the line under your name in the member list, on every server you're on. An empty string clears it.
It's gryt, not window.gryt — your plugin runs in a worker and there's no
window in one. And you don't pass your addon's id: Gryt knows which plugin is
calling, because each one has a worker of its own.
Everything on gryt returns a promise, and it rejects if your plugin hasn't
been given permission. A status that silently doesn't appear is harder to work
out than an error in the console.
What a plugin can reach
A plugin runs in a worker. It can talk to the internet — fetch, WebSocket —
and it can call the gryt API for the things it was given permission for.
That's the whole list. There's no window, no document, no localStorage, no
indexedDB, and no way to start a worker that would have them. Your plugin
can't see Gryt's own code, its sockets, your messages, or the key that is your
identity — not because it promises not to, but because they aren't there.
Anything with a web API works. Spotify is the obvious one. You register an
app with them, poll their currently-playing endpoint, and put the result through
setActivity. Gryt doesn't need to know Spotify exists.
The one wrinkle: an OAuth flow usually wants a popup, and a worker can't open one. Do the sign-in on your own page, keep the refresh token, and have the plugin use it.
Anything a server will do for you works too, if you write the other half. A plugin can send to a copy of itself running on the server and hear back, which is how anything other people can see gets there. Plugin pairs is the whole of it.
Drawing things doesn't. A plugin can't add a button or change how a message looks, because it isn't on the page. If that's what you wanted, Gryt doesn't do it — and the reason is the section below.
Anything that needs the operating system doesn't. Watching which game
you're running means reading the process list, which is Node's job, not the
page's. Gryt would have to expose that from its main process first. That hands
a plugin a list of every program you have open, and lets it put one of their
names in front of everybody on your servers, so it needs a permission of its
own rather than riding on status.
It's on the list. Until it lands, a game plugin has to ask you what you're playing rather than work it out.
Asking for permission
A plugin says what it needs in its manifest:
{
"id": "nowplaying",
"type": "plugin",
"main": "index.js",
"capabilities": ["status"]
}Whoever runs it turns that on in Settings, under the addon. Until they do,
setActivity throws.
Two of them:
| Capability | What it lets a plugin do |
|---|---|
status | Set what you are doing, on every server you are on |
messaging | Exchange its own messages with the servers you are on |
The second is how a plugin talks to a copy of itself running on a server, so what it knows reaches other people — see plugin pairs. It's the one capability where agreeing involves somebody else's server as well as this plugin: what the plugin sends reaches a server you joined, and what comes back was written by whoever runs it.
A name Gryt doesn't recognise is ignored rather than refused, so a manifest written for a newer version still works on an older one. It just doesn't get the new part.
What permission does and doesn't mean
A plugin runs in a worker of its own, and the only thing it can reach is the
gryt API. Gryt checks every call against the manifest and the switch you set,
and refuses what you didn't agree to. There's no other way in — a plugin can't
ignore the API and help itself, because there's nothing else there to take.
That wasn't true until recently. Plugins used to run on the app's own page, and the switches were a plugin telling you what it intended rather than a limit on what it could do. If you read that here before, this is the part that changed.
What it still can't stop: where a plugin sends what you gave it. A plugin keeps its internet connection — one that can't reach Spotify isn't a now-playing plugin. So the switches bound what a plugin can read, not what it does with it afterwards.
That's the honest shape of it. Letting a plugin read your messages is trusting whoever wrote it with those messages, wherever they decide to put them. Install ones you'd trust the author with.
Themes are different again. CSS can't call anything, so the worst a bad theme does is look wrong.
A plugin that sets your status
Make the folder
Settings, Addons, Open folder, then a new folder inside it called
nowplaying.
manifest.json
{
"id": "nowplaying",
"name": "Now playing",
"version": "1.0.0",
"type": "plugin",
"main": "index.js",
"capabilities": ["status"]
}index.js
// Whatever you're reading from. A service you've authenticated with, a local
// player's HTTP API, anything you can fetch. This one cycles two strings so
// you can see it working before wiring the real thing up.
const tracks = ["🎧 Bohemian Rhapsody — Queen", "🎧 Blue Monday — New Order"];
let i = 0;
const timer = setInterval(async () => {
try {
await gryt.setActivity(tracks[i % tracks.length]);
i += 1;
} catch (err) {
// Rejects until the permission is granted. Stop rather than log this every
// thirty seconds forever.
gryt.log.warn(err.message);
clearInterval(timer);
}
}, 30_000);
// Your one chance to tidy up. Clear the status on the way out, or it sits under
// your name until you reconnect.
gryt.on("cleanup", () => {
clearInterval(timer);
gryt.setActivity("").catch(() => {});
});Turn it on
Back in Settings, Addons. Enable it, then turn on Set what you are doing underneath.
When it doesn't work
The addon doesn't appear. Check the folder has a manifest.json with an
id, a name, a version and a type.
setActivity rejects. Either capabilities is missing from the manifest,
or the switch under the addon is off. Both have to be true. The message says
which.
The status doesn't show up. Your role on that server might not allow it. Ask whoever runs it about the Set what you are doing permission.
It disappears after a reconnect. It shouldn't. Gryt re-sends it. If it does, that's a bug worth reporting.
What the server is running
Your addons are yours. The plugins on a server you've joined are somebody else's, and they see what goes through that server.
Every Gryt server names all of them. Open the server menu and pick What this server runs: what each one is, who wrote it, and what it's allowed to do, in words rather than in jargon. If one of them has a half that belongs in your Gryt and you don't have it, that says so too.
Leave is the next row down.
Removing one
Turn it off in Settings, or delete the folder.
Turning a plugin off stops its worker. It gets a cleanup event first, and is
stopped whether or not it finishes — a plugin can't keep running by never
tidying up.
Turning one off leaves its permissions where they were, so switching it back on
doesn't ask again. Deleting the folder clears them, so a different addon can't
later take the same id and inherit what you'd allowed.