Network and secrets
Call APIs with fetch, keep API keys in the Keychain, handle errors.
fetch
Extensions have a fetch much like the browser's. It only goes to https:// URLs on the domains you ask for as network:<domain> in manifest.json.
"permissions": ["network:api.open-meteo.com"]const url = new URL("https://api.open-meteo.com/v1/forecast");
url.searchParams.set("latitude", "41.01");
url.searchParams.set("longitude", "28.97");
url.searchParams.set("current", "temperature_2m");
const res = await fetch(url, { timeout: 10_000 });
if (!res.ok) throw new Error(`Open-Meteo: ${res.status}`);
const data = await res.json<{ current: { temperature_2m: number } }>();Shortcut: ctx.network.fetchJSON<T>(url, init) makes the request and returns .json().
Request body
await fetch("https://api.example.com/items", {
method: "POST",
headers: { authorization: `Bearer ${token}` },
body: { title: "New" }, // object → JSON, content-type set for you
});
await fetch("https://api.example.com/login", {
method: "POST",
body: new URLSearchParams({ user: "ada" }), // form
});A string is sent as is, URLSearchParams as a form, any other value as JSON. Bodies are 1 MB at most.
Errors
| Error | When |
|---|---|
PermissionError | The domain isn't in your network: permissions, an http:// URL, or a redirect to a domain that isn't |
NetworkError | No connection, DNS failure, refused connection, a response over 5 MB, too many requests |
TimeoutError | timeout (15 s by default) passed |
4xx/5xx responses don't throw; check res.ok and res.status.
async update(ctx) {
try {
const data = await ctx.network.fetchJSON<Weather>(url);
ctx.storage.set("last", data); // keep the last good answer
return view(data);
} catch (error) {
const last = ctx.storage.get<Weather>("last");
if (error instanceof NetworkError && last) return view(last, { stale: true });
throw error;
}
}To skip requests while offline, use await AdanotchKit.system.isOnline() and the network.change event.
Secrets
Don't put API keys or passwords in your code or in storage. Declare a secret setting in manifest.json; the user types it in Settings, and Adanotch keeps it in the macOS Keychain.
"settings": [
{ "key": "token", "type": "secret", "label": "GitHub token" }
]async update() {
const token = await AdanotchKit.secrets.get("token");
if (!token) {
return { expanded: ui.text("Enter a GitHub token in Settings.", { tone: "secondary" }) };
}
const res = await fetch("https://api.github.com/user", {
headers: { authorization: `Bearer ${token}`, accept: "application/vnd.github+json" },
});
// …
}secretsettings don't appear inctx.settingsand can't have adefault.secrets.getreturnsnulluntil the user sets a value. When the user saves or deletes it,updateruns again.- Secrets belong to the extension's ID; no other extension can read them.
Don't print a secret with console.log: logs show in Settings › Developer and in the adanotch dev output.
Images
ui.image(src) follows the same list: to show avatars, ask for the image domain too.
"permissions": ["network:api.github.com", "network:avatars.githubusercontent.com"]