Store, seal and updates
Store extensions are signed by Mozilla and sealed by Pulsar, update by themselves, and can be installed from a button on your own site. You can also ship outside the store.
Where extensions come from
| Source | Checks | What people see |
|---|---|---|
| The Pulsar Store | Mozilla signature and Pulsar seal | The install sheet with “Reviewed by Pulsar” |
| Your website or a file | Mozilla signature (your own, unlisted) | The “from outside the store” sheet, following the user’s Ask / Allow / Block setting |
| A Lite extension | None needed (page scripts only) | The same outside-store sheet |
| Chrome, Mozilla or Microsoft stores | — | Refused: Pulsar only installs extensions made for Pulsar |
The Pulsar seal
After review, Pulsar has your file signed by Mozilla (unlisted, never listed on Mozilla’s store) and adds its seal: an Ed25519 signature over the exact signed file.
message = "pulsar-seal-v1:" + lowercase hex sha256 of the signed .xpi
seal = base64( Ed25519 signature of message )- Pulsar checks the seal over the bytes it is about to install. A changed file has no valid seal.
- A file with a valid seal counts as a store extension wherever it was downloaded from (a mirror, a file).
- You never handle the seal. Upload your zip; the store does the rest.
Updates from the store
- Upload a new version with a higher
version. It is reviewed like the first one. - Pulsar checks for updates daily (and on “Check for updates”), checks the file’s hash and seal, and installs it.
- If the new version asks for permissions the old one didn’t have, people decide first. Otherwise it updates silently.
- Your specs: send them again in
Pulsar.onConnect. Values that no longer fit are dropped.
Shipping outside the store
Sign through your own Mozilla account (unlisted) and host the .xpi on your site over https. Add an update_url so people can get updates:
"browser_specific_settings": { "gecko": {
"id": "pricewatch@dealab.example",
"update_url": "https://dealab.example/pulsar/updates.json"
} }{ "addons": { "pricewatch@dealab.example": { "updates": [
{ "version": "1.3.0",
"update_link": "https://dealab.example/pulsar/price-watch-1.3.0.xpi",
"update_hash": "sha256:…" }
] } } }- Updates only come when the user turned on Allow updates from dealab.example at install, only through your
update_url, only from that host (or its subdomains), only over https. update_hashis checked when present. Always add it.- A file from outside the store can never replace a store extension with the same id.
QR codes
Put the store link (or pulsar://plugin/<your gecko id>) in a QR code. Pulsar’s scanner (Extensions › + › Scan a QR code, or the launcher’s Scan QR) shows what it found:
| Code | People see | Buttons |
|---|---|---|
https://plugins.pulsarbrowser.com/p/<slug> or pulsar://plugin/<id> | Extension link found · name | Copy link · View extension |
An https link ending in .xpi or .pulsar.zip | Extension file found · file name | Copy link · Install |
| Any other link | Link found | Copy · Open |
window.pulsarStore
Pulsar’s store pages talk to the browser through window.pulsarStore. It exists only on the Pulsar Store’s own pages (https, top frame), never on other sites, so your site should link to your store page as above. This is what the store page uses:
interface PulsarStore {
version: 1; isPulsar: true; appVersion: string;
getInstalled(): Promise<{ id: string; version: string; enabled: boolean }[]>;
install(id: string): Promise<{ result: 'installed' | 'cancelled' | 'failed'; reason?: string }>;
uninstall(id: string): Promise<{ result: 'removed' | 'cancelled' | 'failed'; reason?: string }>;
update(id: string): Promise<{ result: 'updated' | 'cancelled' | 'failed' | 'none'; reason?: string }>;
setEnabled(id: string, enabled: boolean): Promise<{ ok: boolean }>;
open?(id: string): Promise<{ ok: boolean }>;
onChanged(cb: (e: { type: 'installed' | 'removed' | 'enabled' | 'disabled' | 'updated';
id: string; version?: string; enabled?: boolean }) => void): () => void;
}
// ready check
if (window.pulsarStore) init(); else addEventListener("pulsarstoreready", init);idis the gecko id from your manifest, not the store slug.- Nothing installs by itself:
installalways shows Pulsar’s own sheet, and needs a tap from the user. - One install at a time per tab; at most 30 calls per 10 seconds.