Stacks on #4391. Moves app push into puter.js on the app's own origin, so a website can do:
puter.notifications.onPush('chat', (n) => ({ title: n.title, url: n.target }));
button.onclick = () => puter.notifications.enable();The server picks the route: live over the app's socket while it's connected, a browser push while it isn't.
puter.js
onPush(key, handler): one live feed (onNotification, no catch-up) dispatches bychannel. On websites the handler is also saved to IndexedDB for the worker. Once permission is granted, each call keeps the subscription current without prompting. A hidden tab shows the handler's result itself.enable(): asks for notification permission synchronously (inside the click), then subscribes and confirms. Inside the desktop it isenablePush(). If the user turned the app off in Puter, it asks again through Puter's permission prompt instead of trusting the browser's permission.disable(): ends the subscription and tells the server.- Clicks on websites:
on('click')now also receives taps frompush-sw.js, both from an open page and from a page the worker opened (?puter.notification=plus IndexedDB).
Confirmation. A subscription only counts once a challenge push sent to it comes back. POST /push/web/subscriptions answers pending and the server pushes { type: 'challenge', challenge }. The worker shows it and posts it to the page, and the page sends it to /push/web/subscriptions/verify. A device therefore proves the user allowed notifications in that browser, and that whoever registered it holds the subscription.
push-sw.js (served at js.puter.com/v2/push-sw.js, emitted next to the bundle). Apps import it from their own /puter-sw.js. It:
- shows every Puter push and leaves non-Puter pushes alone;
- runs the saved handler, or one registered in the worker with
puterPush.onPush, with a 5 s limit and a fallback to the message's own title and text; - keeps tap URLs on the app's origin;
- has no install, activate or fetch handlers.
The handler limits are documented on the onPush page.
GUI
sw.jshandles the challenge push.- When the server publishes
pushWebPushKey, a built-in push client (webPushClient.js) uses the same flow through/sw.js, so Settings and the permission dialog work without a provider-specific client.
Presence. EventForwardService.isOnline(userId, appUid) answers whether the pair holds a socket anywhere, so push routing and events share one check.
Fixes from review of the stack
- An app message always has a visible sender. A title made only of invisible or blank-filler characters falls back to the app's name, then its uid.
- Hidden-tab banners show only while push is on in this browser, and for an app's message only when the app holds
notifications:push.
Docs: onPush, enable, disable; Notifications overview; doc/push-client.md.
Tests: puter.js notifications + push-sw.js (vm), GUI suite (1429 passed), static routes, isOnline, app message sender. check:puterjs:types clean. Not run in a real browser yet.
Activity
6Coverage Report for puter.js SDK
| Status | Category | Percentage | Covered / Total |
|---|---|---|---|
| 🔵 | Lines | 58% ⬇️ -1.21 pp | 5454 / 9403 |
| 🔵 | Statements | 55.91% ⬇️ -1.33 pp | 5804 / 10380 |
| 🔵 | Functions | 56.74% ⬇️ -1.88 pp | 989 / 1743 |
| 🔵 | Branches | 50.86% ⬇️ -1.29 pp | 3650 / 7176 |
File Coverage
| File | Stmts | Branches | Functions | Lines | Uncovered Lines |
|---|---|---|---|---|---|
| Changed Files | |||||
| src/puter-js/src/modules/notifications/disable.js | 0% | 0% | 0% | 0% | 13-18 |
| src/puter-js/src/modules/notifications/enable.js | 0% | 0% | 0% | 0% | 18-39 |
| src/puter-js/src/modules/notifications/index.js | 18.57% ⬇️ -36.60 pp | 0% 🟰 ±0 pp | 6.25% ⬇️ -18.75 pp | 21.84% ⬇️ -35.30 pp | 90-270 |
| src/puter-js/src/modules/notifications/onPush.js | 11.42% | 0% | 0% | 12.9% | 14-26, 30-90 |
| src/puter-js/src/modules/notifications/lib/pushStore.js | 25% | 0% | 0% | 32% | 7-14, 18-26, 31, 34, 37 |
| src/puter-js/src/modules/notifications/lib/webPush.js | 13.13% | 0% | 0% | 14.94% | 15-17, 20-22, 26-28, 33-35, 38-66, 71-83, 92-117, 121-213 |
Coverage Report
⬆️ +0.02 pp
⬆️ +0.02 pp
⬆️ +0.02 pp
⬆️ +0.03 pp
File Coverage
⬆️ +0.62 pp
🟰 ±0 pp
⬆️ +4.20 pp
⬆️ +0.65 pp
⬇️ -0.34 pp
⬆️ +0.09 pp
⬆️ +0.09 pp
⬆️ +0.18 pp