Guides
Plugins
Every running stack has a small local HTTP server. Plugins add routes, pages and event listeners to it; braid's own logs and process controls are built the same way.
Using a plugin
List it in plugins, by package name or path, optionally
with options:
export default defineConfig({
processes: [/* ... */],
plugins: [
"@aip-tech/braid-plugin-ui",
["./plugins/notify.js", { channel: "#dev" }],
],
});
Plugins resolve from the config file's directory, so they come from your
project's node_modules. Only listed plugins run.
Plugins run inside braid with full access to Node and to every process in the stack. Use ones you trust, as with any dependency.
Writing a plugin
A plugin is an object with a name and a register function:
import type { BraidPlugin } from "@aip-tech/braid";
const notify: BraidPlugin = {
name: "notify",
register(ctx, options) {
ctx.on("processCrash", (event) => {
ctx.log(`${event.name} crashed (exit code ${event.code})`);
});
ctx.registerRoute("GET", "/api/notify/ping", (_req, res) => {
res.writeHead(200, { "content-type": "text/plain" }).end("pong");
});
},
};
export default notify;
register can be async. If it, or any route or event handler,
throws, braid logs the error and keeps running. Every route is behind the
stack's access token automatically. The full API is in the
Plugin API reference.
Options cross to a background daemon as a structured-clone copy, so they
must be plain data. A function in plugin options makes
braid start fail with a clear error.
Serving a page to the browser
ctx.registerStatic(prefix, dir) serves a directory. Static
files are public, since a page can't attach a token to its own script
loads, so keep secrets out of them. API routes stay protected.
-
Print the page's link with the token in the fragment
ctx.on("controlServerReady", ({ port, token }) => { ctx.log(`open http://127.0.0.1:${port}/#token=${token}`); });Browsers never send the part after
#to a server, so the token stays out of logs. -
Read it once, then send it as a header
const token = new URLSearchParams(location.hash.slice(1)).get("token"); history.replaceState(null, "", location.pathname); await fetch("/api/status", { headers: { Authorization: `Bearer ${token}` }, }); -
For a WebSocket, use a ticket
A browser can't put headers on a WebSocket.
POST /api/ws-ticketreturns a single-use ticket, valid for 30 seconds, to connect with?ticket=<ticket>.
The web dashboard's source is a complete working example.