ctx
ctx is your plugin's context. setup and commands get it as an argument. In a component, get it with usePlugin() from @phantomclient/plugin/vue.
import { definePlugin } from '@phantomclient/plugin';
export default definePlugin({
async setup(ctx) {
const { aj } = ctx.phantom();
const here = await aj.room.current();
ctx.log('in', here?.name ?? 'no room');
const greeting = String(ctx.preferences['greeting'] ?? 'hello');
await ctx.storage.set('lastRoom', here?.name ?? null);
if (ctx.can('game:act')) await aj.chat.say(greeting);
},
});Identity and permissions
ctx.plugin | { id, version }. |
ctx.granted | What the player allowed. |
ctx.can(scope) | Whether a permission was granted. Check it before you offer something the plugin can't do. |
Games
ctx.phantom(id?) | One game, with the whole SDK. Each call is checked against the plugin's permissions. With no id you get the game this code is bound to. It throws NO_GAME if the code isn't bound to one, like a page that sees several games. |
ctx.games() | The games this code can work with right now. An each-game plugin's logic, a panel and a command's view have exactly one. A page and a single plugin see all the games the player allowed. |
ctx.onGames(listener) | Called when the games change (opened, closed, changed room, or allowed differently). Returns a function that stops it. |
Readiness
ctx.ready.wait(what, options) | Waits until the game has all of it, checking with a growing gap. Rejects with NotReady, naming what's still missing, after timeoutMs. Two minutes by default. |
ctx.ready.check(what, options) | Whether the game has all of it now. Never throws. |
ctx.ready.missing(what, options) | Which of them the game doesn't have yet. |
ctx.ready.waitingFor(reason) | Sets the line the player sees about what you're waiting for, in your own words. null clears it. |
what is one of game, room, language, hud, interface, shops, or a list of them. options takes game, which code not bound to a game must pass, and timeoutMs, which only wait reads. Each one needs the permission for the area it reads, and the error names it if you don't have it.
For something the whole plugin needs, declare it in requires instead and Phantom holds setup back.
Storage and preferences
ctx.storage | get, set, remove and keys. Small JSON values Phantom keeps for this plugin, per Phantom account. Settings the player changes belong in preferences instead. |
ctx.config | The settings the manifest's config declares, typed from package.json. A player scoped one reads as the value for the game this code is bound to. Read it where you use it. |
ctx.onConfigChange(listener) | Called with the settings and the names that changed, whoever changed them. Returns a function that stops it. |
ctx.setConfig(values, { game }) | Sets some of your own settings by name, checked as the player's form is. Passwords are refused. |
ctx.preferences | Every preference the manifest declares, by name, at its default until the player sets one. A player scoped one reads as the value for the player of the game this code is bound to. |
ctx.preferenceFields | The preferences the manifest declares, in order: their titles, types and what each needs. |
ctx.preferencesFor(gameId) | The same, for one game's player. For a page or a single plugin's logic, which aren't bound to a game. |
ctx.onPreferences(listener) | Called when any of them changes, whoever changed it. Returns a function that stops it. |
ctx.setPreferences(values, { game }) | Sets some of your own preferences, checked as the player's form is. A player scoped one is saved for the bound game's player unless you name another. Passwords are refused. |
See Preferences for what a preference can declare, and <Preferences /> for drawing the form in a panel of your own.
Keeping state
ctx.keep is state for long-running plugins: read back before you use it, written in batches so a loop can save after every step, kept per Animal Jam account so it survives the game being closed and reopened, and passed to your own panels as it changes.
ctx.keep(name, starting, { game }) | The value kept under that name for one game's player. Code not bound to a game must name one. |
.value | The current value. Change it in place, then save. The reference never changes. |
.ready() | Resolves with the value once what was kept has been read back. |
.save(changes) | Saves it, merging changes in first. Returns at once. |
.flush() | Waits until everything saved has reached the plugin's storage. |
.forget() | Resets it to its starting value. |
.onChange(listener) | Called when it changes, whoever changed it. Returns a function that stops it. |
See Keeping state, including how panels read and change it.
Long-running automations
ctx.automations.all() | Every automation Phantom is supervising for this plugin, on every game this code may work with. |
ctx.automations.status(id, { game }) | Its status and the notes its run has saved. Null if Phantom isn't supervising it. |
ctx.automations.start(id, { game }) | Starts it, as the player would. Resolves once Phantom has the request, not when the run finishes. |
ctx.automations.stop(id, { game }) | Stops it. The run unwinds through its own finally blocks. |
ctx.automations.onChange(listener) | Called when any of them changes. Returns a function that stops it. |
Declare the loop itself in definePlugin({ automations }). See Automations.
Snags
ctx.snag(name, reason) | Says the plugin is running but can't do one of its jobs, and why. null clears it. |
ctx.snags | The current snags, for a plugin that shows them itself. |
Phantom shows a snag beside the preference of that name wherever it's drawn, on the plugin's page, and once in the plugin's log. Use the preference's name when the snag is about a preference. See Saying what isn't working.
Toasts, links, clipboard and network
| Needs | |
|---|---|
ctx.log(...values) | nothing. Writes to the plugin's Log tab. |
ctx.toast(message, { tone, detail }) | nothing. Shows a toast in the app. |
ctx.confirm(message, { detail, accept, decline }) | nothing. Asks in a toast with two buttons. Resolves true, false, or null if ignored. |
ctx.openUrl(url) | nothing. Opens a link in the browser once the player agrees. |
ctx.copy(text) | clipboard |
ctx.notify(title, body) | notifications |
ctx.fetch(url, init) | network. HTTPS only, and only to the hosts the manifest lists. |
const response = await ctx.fetch('https://api.example.com/prices');
if (response.status === 200) ctx.log('Prices updated');Your own panels
ctx.openPanel(panelId, { game }) | nothing. Opens one of your own panels in a game's dock, by its id in contributes.panels. |
ctx.closePanel(panelId, { game }) | nothing. Closes it, like the panel's own close button. |
A panel sits beside one game, so pass game from code that isn't bound to a game: a page, a canvas widget, or a single plugin's logic. Code bound to a game can leave it out.
You can only open panels your own manifest contributes. Another plugin's, or an id you never declared, is refused with PANEL_NOT_ALLOWED. No game passed and none bound gives NO_GAME; a game the player hasn't allowed you on, or one that's closed, gives GAME_NOT_ALLOWED.
Opening a panel the player closed puts it back where it was on the rail, and one already open comes to the front. It opens in that game's dock without switching the player to that game. A panel the player has moved onto the canvas is drawn there instead, so it's already open and stays where they put it.
ctx.phantom().aj.customRooms.onPropClick(({ prop }) => {
if (prop !== 'board') return;
ctx.openPanel('lobby').catch((error: unknown) => ctx.log('no panel', error));
});