Pulsar API
Your background page talks to Pulsar through pulsar.js. You call methods (Pulsar.call) and listen to events (Pulsar.on). This page lists all of them for API version 1.
The client: pulsar.js
Copy pulsar.js into your extension and load it before your own background script. It keeps one connection to Pulsar, matches answers to requests (10 s timeout), reconnects with a growing delay (1 s, 2 s, 4 s … at most 5 min) and wakes your event page when Pulsar has news.
| Function | Returns | Use it for |
|---|---|---|
| Pulsar.call(method, params) | Promise of the result | Any method below. Rejects with an Error that has .code. |
| Pulsar.on(event, fn) | — | One event, or "*" for all. fn(message) gets the event’s fields. |
| Pulsar.onConnect(fn) | — | Runs on every (re)connection with the hello result. Send your specs here. |
| Pulsar.background.schedule(min, name?) | Promise | Shorthand for background.schedule. |
| Pulsar.background.cancel(name?) | Promise | Shorthand for background.cancel. |
| Pulsar.background.keepAlive(reason) | Promise of {jobId, renew(), finish()} | Shorthand for a keepAlive job. |
| Pulsar.engines.call(engine, method, args) | Promise of the engine’s answer | Shorthand for engines.call (waits up to 10 min). |
// pulsar.js is loaded first
Pulsar.onConnect(async (hello) => {
await Pulsar.call("settings.set", mySettingsSpec(hello.settings));
await Pulsar.call("status.set", { text: "Ready" });
});
Pulsar.on("popup.opening", (m) =>
Pulsar.call("popup.set", { reqId: m.reqId, ...myDrawer(m.host) }));
Pulsar.on("popup.action", (m) => console.log(m.item, m.value, m.host));Messages and errors
pulsar.js does this for you, but here is what goes over the wire. Messages are at most 64 KB. Only your extension’s own pages (background, popup, options) may call Pulsar; content scripts must ask the background.
// request (you → Pulsar)
{ "v": 1, "id": "7", "method": "status.set", "params": { "text": "Ready" } }
// answer, same id
{ "v": 1, "type": "reply", "id": "7", "ok": true, "result": {} }
{ "v": 1, "type": "reply", "id": "7", "ok": false,
"error": { "code": "bad_params", "message": "text must be at most 120 characters" } }
// event (Pulsar → you)
{ "v": 1, "type": "event", "event": "popup.action", "item": "ads", "value": false, … }| Error code | Means |
|---|---|
| bad_message | Not a valid message (not JSON, missing fields, over 64 KB). |
| unsupported_version | A v Pulsar doesn’t know. New methods and fields are only ever added. |
| unknown_method | No such method. |
| bad_params | The parameters are wrong. message says exactly what, for example sections[0].items[2].value must be true or false. |
| not_allowed | No "pulsar": {"api": 1}, a missing Pulsar permission, or a call from a content script. |
| plugin_disabled | The user (or Pulsar’s kill switch) switched the extension off. |
| failed | Something went wrong on Pulsar’s side. Try again later. |
| timeout, disconnected | From pulsar.js: no answer in 10 s, or Pulsar went away. It reconnects by itself. |
try {
await Pulsar.call("popup.set", spec);
} catch (e) {
if (e.code === "bad_params") console.error("Fix the spec:", e.message);
// timeout / disconnected: onConnect will run again when Pulsar is back
}hello
Sent by pulsar.js on every connection; its result is what Pulsar.onConnect gets. You rarely call it yourself.
| Result field | Type | Meaning |
|---|---|---|
| api | number | The API version Pulsar speaks. |
| id | string | Your extension id. |
| version | string | Your installed version. |
| settings | object | Your settings values: {id: value} (defaults plus the user’s choices). |
| siteAccess | string | Where the user lets you run: All sites, Ask me, or Selected sites. |
| allowedSites | string[] | Sites the user allowed (Ask me / Selected). |
| disabledSites | string[] | Sites where the user switched you off. Don’t run there. |
popup.set
Sets your drawer: an ActionSpec (title, optional subtitle, optional header toggle, sections). Add reqId when you answer popup.opening. Call it again any time (after popup.action, say) and an open drawer updates in place.
| Param | Type | Meaning |
|---|---|---|
| title | string, required | Your drawer’s title (usually your name). |
| subtitle | string | One line under the title, like “On for news.example”. |
| toggle | {id, value, title?, enabled?} | A switch at the right of the title. Sends popup.action like a toggle. Its id must differ from every item id. |
| sections | Section[] | See Native UI. |
| reqId | string | The reqId of the popup.opening you are answering. |
Result: {}. Errors: bad_params with the reason.
Pulsar.on("popup.opening", async (m) => {
await Pulsar.call("popup.set", {
reqId: m.reqId,
title: "Price Watch",
subtitle: m.host ? `Watching ${m.host}` : "Open a shop to watch prices",
sections: [{ title: null, items: [
{ type: "toggle", id: "watch", title: "Watch this item", value: false, icon: "star" },
{ type: "cta", id: "list", label: "Open my list", primary: true },
] }],
});
});settings.set
Sets your settings page, drawn by Pulsar in Settings › Extensions › your name: a SettingSpec ({sections}). The values in the spec are the defaults; Pulsar keeps the user’s choices on top.
| Value | |
|---|---|
| Params | A SettingSpec: { sections: Section[] } |
| Result | { values }: every setting’s value now (defaults plus the user’s choices). |
Send it again when your spec changes (a new version, a list that grew). Values that no longer fit the new spec are dropped.
const { values } = await Pulsar.call("settings.set", { sections: [
{ title: "Alerts", items: [
{ type: "toggle", id: "notify", title: "Tell me when a price drops", value: true, icon: "bell" },
{ type: "choice", id: "every", title: "Check", value: "day",
options: [{ value: "hour", label: "Every hour" }, { value: "day", label: "Every day" }] },
] },
] });
console.log(values.notify, values.every);settings.get
No params. Result: { values }. You get the same in hello.settings and in every settings.changed event.
const { values } = await Pulsar.call("settings.get");status.set
The one line under your name in Pulsar’s lists and in your drawer’s header, like “14 blocked here”. Keep it short.
| Param | Type | Meaning |
|---|---|---|
| text | string (≤ 120) or null | The line. null clears it. |
| tabId | string | Only for this tab (Pulsar’s tab id from an event). Without it, for every tab. |
await Pulsar.call("status.set", { text: "On for 3 sites" });
await Pulsar.call("status.set", { text: "14 blocked here", tabId: m.tabId });background.start, renew, finish
Needs "pulsar": {"permissions": ["background"]}. Background work belongs to Pulsar: the user sees it in Settings › Background activity and can stop it. Full guide: Background work.
| Method | Params | Result |
|---|---|---|
| background.start | {type: "monitor" | "keepAlive", title} (title ≤ 80) | {jobId, expiresInMin?} |
| background.renew | {jobId}. Only on an open connection. | {expiresInMin: 10} |
| background.finish | {jobId} | {} |
const job = await Pulsar.background.keepAlive("Checking 40 prices");
try {
for (const item of items) { await check(item); if (mid(item)) await job.renew(); }
} finally {
await job.finish();
}background.schedule, cancel
A check that repeats, run by Android’s WorkManager. Pulsar sends you schedule.run when it is due, waking your event page if needed. Android’s floor is 15 minutes, and Android may run it late.
| Method | Params | Result |
|---|---|---|
| background.schedule | {name: [A-Za-z0-9._-]{1,40}, intervalMin} | {intervalMin}: the interval used (at least 15) |
| background.cancel | {name} | {} |
Pulsar.onConnect(() => Pulsar.background.schedule(60, "prices"));
Pulsar.on("schedule.run", async (m) => { if (m.name === "prices") await checkPrices(); });engines.call
Calls one of Pulsar’s engines (Player, Files, Converter). Needs the engine in "pulsar": {"engines": [...]}. The answer comes when the engine is done (a picker may be open), so it can take minutes. See Engines.
| Param | Type | Meaning |
|---|---|---|
| engine | string | player, files or converter. |
| method | string | The engine’s method, like convert. |
| args | object | The method’s arguments (at most 64 KB of JSON). |
Result: {ok: true, …} or {ok: false, error, message} with error one of denied, blocked, unknown_engine, unknown_method, bad_args, busy, failed.
const r = await Pulsar.engines.call("converter", "convert", { to: "mp3" });
if (r.ok) Pulsar.call("status.set", { text: `Saved ${r.name}` });
else if (r.error !== "denied") console.warn(r.error, r.message);Events
Events arrive on your open connection. If none is open, Pulsar keeps the last 50 and wakes your event page. tabId is Pulsar’s tab id (not the tabs API id). host is the page’s host without www., or null on Home and Pulsar’s own pages.
| Event | Fields | When |
|---|---|---|
| popup.opening | reqId, tabId, url, host, private | The user tapped your tile. Answer with popup.set and the same reqId within 400 ms (1.5 s if Pulsar had to wake you), or Pulsar shows your last spec. |
| popup.action | tabId, url, host, item, value | A control in your drawer changed or was tapped. See the value table below. |
| popup.closed | — | The drawer closed. Only on an open connection (it never wakes you). Go back to your first view here. |
| settings.changed | item, value, values | The user changed a setting. values is the full set. Buttons send value: null. |
| site.access | siteAccess, allowedSites, disabledSites | The user changed where you may run. Stop working on `disabledSites`. |
| schedule.run | name | A scheduled check is due. |
| job.stopped | jobId | Pulsar or the user stopped a background job. |
What value a control sends
| Item type | value in popup.action / settings.changed |
|---|---|
| toggle, header toggle | true or false |
| choice, chips | The chosen option’s value |
| slider | A number |
| text | A string |
| button, cta, media | null (a tap) |
// Keep popup.opening quick: read state you already have in memory, then answer.
let state = await loadState(); // once, at start
Pulsar.on("popup.opening", (m) =>
Pulsar.call("popup.set", { reqId: m.reqId, ...drawerFor(m.host, state) }));
Pulsar.on("site.access", async (m) => {
state.disabledSites = m.disabledSites; // content scripts read this and stop there
await browser.storage.local.set({ disabledSites: m.disabledSites });
});One-shot calls without pulsar.js
If you only need a method now and then (no events), you can send a single message to Pulsar’s core extension yourself:
const reply = await browser.runtime.sendMessage("core@pulsarbrowser.com",
{ v: 1, id: "1", method: "status.set", params: { text: "Ready" } });
if (!reply.ok) console.warn(reply.error.code);background.renew and all events need the connection that pulsar.js keeps, so most extensions should just use pulsar.js.