A push notification reaches the user when they have neither Puter nor your app open. It shows on their phone or desktop, and a tap opens your app. Your app sends it with notify(), the same call that puts a notification in the user's Puter notifications panel. Puter decides the route. While your app is open and connected, the notification reaches the page live. While it isn't, it goes out as a push.
This page is the setup. A website on its own domain needs three steps, and an app inside the Puter desktop needs one.
On a website, pushes arrive on your own domain through a small service worker, and the browser asks the user for permission on your domain.
Put this file at the root of your site, as /puter-sw.js:
importScripts('https://js.puter.com/v2/push-sw.js');
If your site already has a service worker, add that line to it and pass its path as serviceWorker to onPush() and enable().
On every page load, register a handler with onPush() for each kind of notification you send. The key is the channel you pass to notify().
puter.notifications.onPush('chat', (n) => ({
title: `${n.app?.name ?? 'Chat'}: ${n.title}`,
url: n.target ?? '/',
}));
While your app is open, the handler runs in the page as each notification arrives. While it isn't, the same handler runs in your service worker to decide what the push shows. The handler is saved as source and run again in the worker, so it has these limits:
window, document or localStorage. fetch, indexedDB and caches are available.Returning nothing shows the notification's own title and text. The fields a handler can return are listed on onPush().
Call enable() from a click. Browsers only show their permission prompt from a user gesture.
document.getElementById('enable').addEventListener('click', async () => {
const on = await puter.notifications.enable();
puter.print(on ? 'Notifications are on<br>' : 'Notifications are off<br>');
});
The browser asks for permission on your domain. Puter then sends a confirmation push to that browser, and the browser is registered once your worker hands the confirmation back. Allowing it also lets your app's notifications go out as pushes. On later visits, onPush() keeps the registration current without asking again.
enable() resolves true when pushes are on in this browser, and false when the user didn't allow notifications or the browser can't receive pushes. It rejects with push_revoked when the user turned your app off in Puter's settings. That choice stands until the user allows your app again, so call requestPermission() from a click, which opens Puter's permission prompt, and then call enable() again:
document.getElementById('enable').addEventListener('click', async () => {
try {
await puter.notifications.enable();
} catch (error) {
if (error.code !== 'push_revoked') throw error;
if (await puter.notifications.requestPermission()) {
await puter.notifications.enable();
}
}
});
To turn pushes off in this browser again, call disable(). Notifications still reach your app live while it's open.
Send the notification with notify(), with the handler's key as channel:
await puter.notifications.notify({
title: 'New message from Ana',
text: 'See you at 6?',
target: '/chats/42',
data: { chatId: 42 },
channel: 'chat',
});
It works from the page and from a worker. The notification always shows in the user's Puter notifications panel. It goes out as a push only while the user has neither your app nor Puter open, and a user gets at most one push a minute from each app. The other limits are on Rate Limits and Quotas.
When the user taps the push, the browser opens the handler's url on your domain. When the user clicks the notification in the Puter panel instead, Puter opens your app and hands the target and data back through on('click').
Serve the page below from a site that has /puter-sw.js to try it. It registers the handler, turns pushes on from a click, and sends a notification. The notification arrives live while the page is open, and as a push while it isn't.
<html>
<body>
<script src="https://js.puter.com/v2/"></script>
<button id="enable">Turn on notifications</button>
<button id="send">Send me a notification</button>
<script>
// Runs in the page while it's open, and in /puter-sw.js while it isn't.
puter.notifications.onPush('chat', (n) => ({
title: n.title,
body: n.text ?? '',
url: n.target ?? '/',
}));
document.getElementById('enable').addEventListener('click', async () => {
const on = await puter.notifications.enable();
puter.print(on ? 'Pushes are on<br>' : 'Pushes are off<br>');
});
document.getElementById('send').addEventListener('click', async () => {
await puter.notifications.notify({
title: 'New message from Ana',
text: 'See you at 6?',
target: '/chats/42',
channel: 'chat',
});
puter.print('Sent<br>');
});
</script>
</body>
</html>
An app inside the Puter desktop needs no worker and no enable(). Pushes go through Puter itself. Ask once, from a click:
const allowed = await puter.notifications.requestPermission();
Puter's permission dialog opens. When the user allows it, Puter also turns pushes on in the browser they answered in, and your notifications go out as pushes from then on. For a user who already allowed your app, in a browser where pushes aren't on yet, enablePush() shows a one-button dialog that turns them on. hasPermission() says whether the user allowed your app, without asking, so you can decide whether to show a button at all.
While the app is open, onPush() and onNotification() receive its notifications live, the same as on a website.
data field is handed back on click, and push services can read it, so put IDs in it and fetch the rest. Set sensitive: true when the title and text shouldn't leave Puter. The push then shows a generic line, and the panel still shows the notification.data at most 1 KB.url a handler returns has to be on your origin, and other origins are replaced with your site's root.