Overview
Add browser push notifications with usePushNotifications, inside the SaaSOSProvider you already have.
BuildBase provides browser push notifications out of the box. SaaSOSProvider
(also exported as BuildBaseProvider) already mounts the push provider, so
usePushNotifications() works anywhere inside it and there is nothing extra to
wrap. The Notifications screen of the built-in settings
uses the same provider, so it has its own subscribe button.
What is left to you is serving the service worker and, if you want one, a control of your own:
import { usePushNotifications } from '@buildbase/sdk/react';
function NotificationBell() {
const {
isSupported,
permission,
isSubscribed,
loading,
error,
subscribe,
unsubscribe,
} = usePushNotifications();
if (!isSupported) return null;
if (permission === 'denied') return <p>Notifications are blocked.</p>;
return (
<>
<button
disabled={loading}
onClick={isSubscribed ? unsubscribe : subscribe}
>
{isSubscribed ? 'Turn off notifications' : 'Turn on notifications'}
</button>
{error && <p role="alert">{error}</p>}
</>
);
}subscribe() asks for permission itself, so there is no separate
requestPermission() call before it. It does nothing for a signed-out visitor:
a subscription belongs to a user, so render the control where someone is signed
in.
Warning
There is no PushNotificationProvider to add. It reads the org and the
signed-in user from SaaSOSProvider, so outside it, it throws (useOSStore must be used within a OSProvider), and inside it there is already one.
Serve the service worker
When a user subscribes, the SDK registers the service worker at /push-sw.js
on your origin. The SDK does not host that file. Your app has to serve it from
that path, or subscribe() fails when the browser cannot fetch the worker.
The worker script ships in the SDK as the PUSH_SERVICE_WORKER_SCRIPT string,
exported from both @buildbase/sdk and @buildbase/sdk/react. Write it into
your public directory at build time, so it updates with the SDK:
// scripts/write-push-sw.mjs
import fs from 'node:fs';
import { PUSH_SERVICE_WORKER_SCRIPT } from '@buildbase/sdk';
fs.writeFileSync('public/push-sw.js', PUSH_SERVICE_WORKER_SCRIPT);{
"scripts": {
"predev": "node scripts/write-push-sw.mjs",
"prebuild": "node scripts/write-push-sw.mjs"
}
}Note
Rich notifications need @buildbase/sdk
0.0.83
or later, and a push-sw.js written from
PUSH_SERVICE_WORKER_SCRIPT at build time, as above. That worker is what
shows image, badge, action buttons, tag replacement,
requireInteraction, silent, renotify, timestamp and dir. The 0.0.82
worker still shows the title, body and icon and opens the tracked link, and
ignores the rest. A copy pasted into public/ by hand stays the version you
copied: upgrading the SDK does not change it.
Serving it from another path
The path is fixed at /push-sw.js. SaaSOSProvider mounts its push provider
with no options and has no prop to change that, and the settings screen and
every usePushNotifications() call under it register and look up that path.
To keep the file somewhere else, answer /push-sw.js with it through a rewrite
in your framework or host. Not a redirect: browsers refuse a worker script that
redirects.
A second PushNotificationProvider nested inside SaaSOSProvider with its own
serviceWorkerPath changes only the usePushNotifications() calls below it.
The settings screen keeps /push-sw.js, so it can show a different state from
your control, and its button registers the default worker, not yours. If you
nest one, hide that block with
ui={{ settings: { notifications: { push: false } } }} on SaaSOSProvider, so
your control is the only one. The provider's
autoSubscribe prop is accepted and does nothing.
usePushNotifications() returns
| Return | Type | Description |
|---|---|---|
permission | 'default' / 'granted' / 'denied' | Browser notification permission |
isSubscribed | boolean | Whether this browser holds a push subscription. Checked once a user is signed in, and false until that check finishes |
isSupported | boolean | Whether the browser supports service workers, push and notifications |
loading | boolean | true while subscribe() or unsubscribe() runs |
error | string | null | Why the last subscribe() or unsubscribe() failed, including a refused permission. Cleared when the next one starts |
requestPermission() | () => Promise<boolean> | Ask for notification permission without subscribing. Resolves true when granted |
subscribe() | () => Promise<void> | Ask for permission if needed, subscribe this browser and register it with BuildBase. Does not reject: a failure sets error |
unsubscribe() | () => Promise<void> | Unsubscribe this browser and remove it from BuildBase. Does not reject: a failure sets error |
Before it sends
In production, each of these has to hold before a notification reaches a browser:
- An HTTPS origin serving
/push-sw.js. Browsers only register service workers and subscribe to push on a secure origin.localhostcounts while you develop. - A browser push service. The server only accepts a subscription whose endpoint belongs to the push services run by Google, Mozilla, Microsoft and Apple, the ones Chrome, Firefox, Edge and Safari subscribe through. Any other endpoint is refused and never stored, since the server would be calling it on every send.
- A plan that includes push notifications. On a plan without them nothing is
pushed, though the inbox item, and the email if the event has a template,
still go. If the org has no VAPID keys yet,
subscribe()also fails, with "Push notifications are not available right now." - Push switched on for the org and the event. The org switch is Push
notifications under Messaging > Notification events > Global in the
console. Off, campaigns are refused and your events send no push. Each event has its own
push channel, and an event registered by its first
send()starts with push on. Members' own preferences come after that - see the four gates. - VAPID keys. Every push is signed with a key pair that belongs to the org. Generate it under Messaging > Push campaigns, or let the first browser that subscribes create it. Until it exists, sends skip push.
- Room under the plan's push subscriber limit. At the limit a new browser is
turned away:
subscribe()fails anderror, like the settings screen, reads "Push notifications are not available right now." It names no plan, because the person reading it is your user. Browsers already subscribed keep receiving, and you are told through theorganization.end_user_ceiling_reachedwebhook event. - Push left in the plan's allowance. Every device a push is delivered to
counts against the plan's push allowance for the period. On a plan that allows
no overage, once what is left cannot cover a person's devices, their push is
skipped and the inbox item still goes. You are told through the same
organization.end_user_ceiling_reachedevent.
Each time a browser subscribes, it gets one "Notifications Enabled" notification straight away, which is the quickest way to see the whole path work.
Sending notifications (server-side)
Use the server SDK to send notifications to users:
import BuildBase from '@buildbase/sdk';
const bb = BuildBase({ serverUrl, orgId, getSessionId });
// Send to a specific user
await bb.notification.send(workspaceId, 'comment-added', userId, {
title: 'New comment',
message: 'Someone commented on your post.',
url: '/posts/123',
});
// Send to all workspace members
await bb.notification.send(workspaceId, 'new-release');The rich fields (image, badge, tag, actions and the rest) go in the same
data object, and the exported NotificationData type has every one of them.
Notifications explains the common ones, along with
the gates every send passes.
Next Steps
- Notifications - One call for email and push, and the gates it passes.
- Webhooks - React to events server-side.
- Server SDK - All server methods including notifications.