Realm event in. Browser notification out.
TurnPlayed game=0000042 next=g1k7…x2q turn=7
Mentioned channel=general by=g1zm…9ra who=g1k7…x2q
ProposalClosed id=0000012 result=passed
Outbid lot=0000309 by=g1p0…7cd was=g1k7…x2q
UnlockReached height=1300000 account=g1k7…x2q
How it works
gnotif sends browser notifications when your realm emits an event, even when the page that asked for them is closed. A trigger in the gnotif registry says who an event notifies. The public server at https://gnotif.xyz watches the testnet and sends the push, so there is no notification server to run.
- dapp realmemits events, declares its trigger
- gnotif registryholds the triggers, on chain
- tx-indexerreads the chain, serves GraphQL
- gnotifdmatches events, sends the pushes
- push serviceWeb Push, run by the browser's vendor
- sw.jsshows the notification, on the dapp's origin
The page calls gnotifd only to subscribe and to set its opt-ins. The notification travels through the browser's push service and is shown by the service worker the dapp serves from its own origin.
Add notifications to a dapp
Emit
Emit an event from your realm
pingpong, the demo game, names the next player on every turn.
Getting started →chain.Emit("TurnPlayed", "game", g.ID, "next", g.Next.String(), "turn", strconv.Itoa(g.Turn))Declare
Declare a trigger in the registry
The realm declares the trigger itself, so the registry marks it verified. Call DeclareTriggers once after the deploy.
import "gno.land/r/<namespace>/gnotif/v0"
var declared bool
func DeclareTriggers(cur realm) {
if declared {
panic("triggers already declared")
}
declared = true
gnotif.Declare(cross(cur), cur.PkgPath(), "TurnPlayed", "", "next",
"Your turn", "Game {game}, turn {turn}", "/?game={game}")
}Subscribe
Subscribe the browser from your page
Copy node_modules/gnotif/src/sw.js to the folder your site serves at its root. From a click, call enable(), then opt the browser in with the player's address.
import { Gnotif } from "gnotif";
const gnotif = new Gnotif({ server: "https://gnotif.xyz" });
const yourTurn = (await gnotif.triggers()).find(
(t) =>
t.target === "gno.land/r/<namespace>/pingpong/v0" &&
t.event === "TurnPlayed" &&
t.verified,
);
if (yourTurn === undefined) {
throw new Error("This gnotif server does not offer pingpong's trigger.");
}
button.addEventListener("click", async () => {
await gnotif.enable();
await gnotif.setOptins([{ trigger: yourTurn.id, value: playerAddress }]);
});Operate
Run your own gnotifd
gnotifd is one binary with one SQLite file. It reads a tx-indexer and signs its pushes with a VAPID key pair. A container image is published for every release.
Running gnotifd →