notch.monster
DownloadGet it

Services

Using built-in services

List the services you use and the scopes you need. Sensitive scopes need a justification, keyed by the permission behind them.

"uses": { "monster.notch.timer/timer@1": ["read", "control"] },
"justifications": { "timer:control": "Starts a focus session for the picked task." }
import { notch } from "@notch-monster/plugin-sdk";
import { SERVICES } from "@notch-monster/plugin-sdk/protocol";

const timer = notch.service(SERVICES.timer);
if (await timer.available()) await timer.start({ minutes: 25 });
timer.on("changed", async () => render(await timer.get()));
Service read write / control
SERVICES.timer get() start({ minutes?, mode?, task? }), pause(), resume(), reset(), skip(), extend({ minutes })
SERVICES.tasks list({ day? }), get({ id }) add({ title, day? }), complete({ id }), setCurrent({ id })
SERVICES.notes current(), pages() append({ text }), newPage({ text? })
SERVICES.media get() control({ action })

Each built-in service raises a changed event.

Providing a service

Any plugin can offer services. Declare them in provides, keyed <name>@<major>. Other plugins reach them as <your id>/<name>@<major>.

"provides": {
  "reading-list@1": {
    "scopes": {
      "read": { "text": "See your reading list" },
      "write": { "text": "Add to your reading list", "sensitive": true }
    },
    "methods": { "list": "read", "add": "write" },
    "events": { "changed": "read" }
  }
}

The app runs your service frame: your entry page loaded with ?view=service, hidden. It starts when someone calls and stops a minute after the last call. Register handlers before ready():

if (new URLSearchParams(location.search).get("view") === "service") {
  notch.provide("reading-list@1", {
    list: async () => (await notch.storage.plugin.get("items")) ?? [],
    add: async ({ title }, caller) => {
      // caller: { id, name } of the plugin asking
      await notch.emit("reading-list@1", "changed", {});
      return { ok: true };
    },
  });
}
await notch.ready();

A handler’s return value (or thrown error) is the caller’s answer. Each call has 15 seconds. Consumers list your service in uses the same way as a built-in one: "uses": { "com.example.reader/reading-list@1": ["read"] }.

Limits: 8 services per plugin; 8 scopes, 32 methods and 16 events per service; scope text up to 80 characters. For a breaking change, bump the major version and keep the old one for a while.