Push notifications
One call in your page turns on notifications for iOS, Android and the web. Devices register as the signed-in user, and your server sends to people and topics while Dispatch handles APNs, FCM and Web Push.
#How it works
await pushNotifications.enable()#In your pages
Push uses pushNotifications from @absolutejs/devices. Importing it is what adds the native push plugin and its permissions to your app.
import { pushNotifications } from '@absolutejs/devices';
// From a "Turn on notifications" button. Asks for permission if it
// has not been asked yet, then registers this device with your server.
await pushNotifications.enable();
// Stop sending to this device.
await pushNotifications.disable();Listen for notifications while the app is open, and for taps. The deepLink you sent arrives in notification.data.absoluteDeepLink; route to it with your app’s own navigation.
import { pushNotifications } from '@absolutejs/devices';
// A notification arrived while the app was open.
const stopReceived = await pushNotifications.onReceived((notification) => {
showToast(notification.title, notification.body);
});
// The user tapped a notification or one of its actions.
const stopActions = await pushNotifications.onAction(({ actionId, notification }) => {
const link = notification.data.absoluteDeepLink;
if (actionId === 'tap' && typeof link === 'string') openInApp(link);
});
// When the component unmounts
await stopReceived();
await stopActions();#On your server
Create one push lifecycle with an adapter for each kind of device, and storage for registrations. Apply PUSH_SUBSCRIPTION_POSTGRES_SCHEMA from @absolutejs/dispatch-push-postgres and IDEMPOTENT_OPERATION_POSTGRES_SCHEMA from @absolutejs/reliability to your database once.
import { createPushLifecycle } from '@absolutejs/dispatch';
import { createApnsAdapter } from '@absolutejs/dispatch-apns';
import { createFcmAdapter } from '@absolutejs/dispatch-fcm';
import {
createPostgresPushFanoutClaimStore,
createPostgresPushSubscriptionStore
} from '@absolutejs/dispatch-push-postgres';
import { createWebPush } from '@absolutejs/pwa';
import { createWebPushDispatchAdapter } from '@absolutejs/pwa/dispatch';
import {
createPostgresIdempotentOperationStore,
createPostgresTransactionRunner
} from '@absolutejs/reliability';
const apns = createApnsAdapter({
bundleId: 'com.example.shop',
keyId: process.env.APNS_KEY_ID!,
privateKey: process.env.APNS_PRIVATE_KEY!,
teamId: process.env.APNS_TEAM_ID!
});
const fcm = createFcmAdapter({ projectId: process.env.FCM_PROJECT_ID! });
const webPush = createWebPush({
publicKey: process.env.VAPID_PUBLIC_KEY,
privateKey: process.env.VAPID_PRIVATE_KEY,
subject: 'mailto:alerts@example.com'
});
const runner = createPostgresTransactionRunner(pool);
export const pushLifecycle = createPushLifecycle({
adapterFor: (subscription) => {
if (subscription.platform === 'apns') return apns;
if (subscription.platform === 'fcm') return fcm;
return createWebPushDispatchAdapter(webPush, subscription);
},
claimStore: createPostgresPushFanoutClaimStore(
createPostgresIdempotentOperationStore(runner)
),
store: createPostgresPushSubscriptionStore(runner)
});Then give it to Auth. That mounts /auth/push, which accepts the app’s signed-in requests and the browser’s session cookie. Push needs Auth’s oidc provider, which your app already uses to sign in.
import { auth } from '@absolutejs/auth';
import { pushLifecycle } from './push';
const authApplication = await auth({
getUser,
providersConfiguration,
oidc: oidcConfiguration,
push: {
registrar: pushLifecycle,
tenant: (principal) => principal.user.organizationId,
topics: (principal) => ['orders', `store:${principal.user.storeId}`]
}
});pushNotifications but @absolutejs/auth is not installed or auth({ push }) is not configured.#Sending
// Every device of one user
await pushLifecycle.send(
{ tenant: 'acme', userId: order.customerId },
{
title: 'Your order shipped',
body: 'Arriving Thursday.',
deepLink: '/orders/' + order.id,
idempotencyKey: `order:${order.id}:shipped`
}
);
// Everyone subscribed to a topic
await pushLifecycle.send(
{ tenant: 'acme', topic: 'orders' },
{ title: 'New order', body: 'Order 1042 is waiting.' }
);| Send to | Reaches |
|---|---|
{ tenant, userId } | Every device of one user. |
{ tenant, topic } | Every device subscribed to a topic. |
{ tenant, deviceId } | One device. |
{ tenant, subscriptionIds } | Specific registrations. |
| Message field | Meaning |
|---|---|
body | The text. Required. |
title | The heading. |
deepLink | Where a tap should go. Arrives as data.absoluteDeepLink in the app, and opens that URL from a browser notification. |
data | Extra values your app reads in onReceived or onAction. |
badge | The number on the app icon. |
sound | A notification sound. |
actions | Buttons shown on the notification. |
idempotencyKey | Makes a retry of the same send deliver once. |
#Platform setup
| Platform | You need | What AbsoluteJS does |
|---|---|---|
| iOS | An APNs key (.p8) from your Apple Developer account, with its key ID and your team ID. | The push entitlement and the code that receives the device address are added to the iOS project for you. For builds run from Xcode, create the APNs adapter with environment set to sandbox. |
| Android | A Firebase project with an Android app whose package name is your appId. | Put its google-services.json in your project; absolute mobile sync checks it matches your appId and copies it in. Your server sends with a Google service account. |
| Web | A VAPID key pair. | Add a pwa block to absolute.config.ts. Pages that import pushNotifications get Web Push in the browser and the installed web app. |
mobile: {
appId: 'com.example.shop',
appName: 'Shop',
server: { productionOrigin: 'https://shop.example.com' },
pushNotifications: {
android: { googleServicesFile: 'google-services.json' } // the default path
}
}The same pushNotifications code sends Web Push to the browser and the installed web app once your config has a pwa block and the build has a public VAPID key.
// absolute.config.ts: turn on the installable web app
pwa: {
manifest: {
name: 'Shop',
shortName: 'Shop',
icons: [{ src: '/icons/icon-512.png', sizes: '512x512', type: 'image/png' }]
}
}
// .env: the public key is built into the page; the private key stays on the server
// VAPID_PUBLIC_KEY=...
// VAPID_PRIVATE_KEY=...#Delivery guarantees
#Local notifications
For reminders the app can schedule itself, use localNotifications instead. They need no server and fire with no connection.
| Feature | Local notifications | Push notifications |
|---|---|---|
| Who sends it | Your app, on the device | Your server |
| Works with no connection | ||
| Needs Auth and a server | ||
| Typical use | Reminders, timers, pickups | Messages, orders, alerts |
import { localNotifications } from '@absolutejs/devices';
const permission = await localNotifications.requestPermission();
if (permission.state === 'granted') {
await localNotifications.schedule({
id: 7,
title: 'Pickup reminder',
body: 'Your order is ready at 5 pm.',
scheduledAtMs: pickupTime - 30 * 60 * 1000
});
}#Packages
Every device feature, including the permission states push uses, is on Device APIs.
@absolutejs/dispatchThe push lifecycle: registration, targeting, retries and retirement.
@absolutejs/dispatch-apnsDelivery to iPhone and iPad through Apple.
@absolutejs/dispatch-fcmDelivery to Android through Firebase Cloud Messaging.
@absolutejs/dispatch-push-postgresPostgreSQL storage for registrations and sends.
@absolutejs/pwaWeb Push, the installable web app and its service worker.