Developers

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.

FunctionReturnsUse it for
Pulsar.call(method, params)Promise of the resultAny 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?)PromiseShorthand for background.schedule.
Pulsar.background.cancel(name?)PromiseShorthand 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 answerShorthand for engines.call (waits up to 10 min).
background.js
// 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.

Wire format
// 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 codeMeans
bad_messageNot a valid message (not JSON, missing fields, over 64 KB).
unsupported_versionA v Pulsar doesn’t know. New methods and fields are only ever added.
unknown_methodNo such method.
bad_paramsThe parameters are wrong. message says exactly what, for example sections[0].items[2].value must be true or false.
not_allowedNo "pulsar": {"api": 1}, a missing Pulsar permission, or a call from a content script.
plugin_disabledThe user (or Pulsar’s kill switch) switched the extension off.
failedSomething went wrong on Pulsar’s side. Try again later.
timeout, disconnectedFrom pulsar.js: no answer in 10 s, or Pulsar went away. It reconnects by itself.
Handling errors
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 fieldTypeMeaning
apinumberThe API version Pulsar speaks.
idstringYour extension id.
versionstringYour installed version.
settingsobjectYour settings values: {id: value} (defaults plus the user’s choices).
siteAccessstringWhere the user lets you run: All sites, Ask me, or Selected sites.
allowedSitesstring[]Sites the user allowed (Ask me / Selected).
disabledSitesstring[]Sites where the user switched you off. Don’t run there.

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
ParamsA 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.

js
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.

js
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.

ParamTypeMeaning
textstring (≤ 120) or nullThe line. null clears it.
tabIdstringOnly for this tab (Pulsar’s tab id from an event). Without it, for every tab.
js
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.

MethodParamsResult
background.start{type: "monitor" | "keepAlive", title} (title ≤ 80){jobId, expiresInMin?}
background.renew{jobId}. Only on an open connection.{expiresInMin: 10}
background.finish{jobId}{}
js
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.

MethodParamsResult
background.schedule{name: [A-Za-z0-9._-]{1,40}, intervalMin}{intervalMin}: the interval used (at least 15)
background.cancel{name}{}
js
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.

ParamTypeMeaning
enginestringplayer, files or converter.
methodstringThe engine’s method, like convert.
argsobjectThe 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.

js
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.

EventFieldsWhen
popup.openingreqId, tabId, url, host, privateThe 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.actiontabId, url, host, item, valueA 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.changeditem, value, valuesThe user changed a setting. values is the full set. Buttons send value: null.
site.accesssiteAccess, allowedSites, disabledSitesThe user changed where you may run. Stop working on `disabledSites`.
schedule.runnameA scheduled check is due.
job.stoppedjobIdPulsar or the user stopped a background job.

What value a control sends

Item typevalue in popup.action / settings.changed
toggle, header toggletrue or false
choice, chipsThe chosen option’s value
sliderA number
textA string
button, cta, medianull (a tap)
Answer fast
// 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:

js
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.