Rising Tide Developers
SDKCore

rtClient.push

SDK methods for Web Push: a device subscribes, and the notification feed reaches its lock screen.

Web Push subscriptions for the signed-in person. What gets pushed is the protocol's notification feed - the same items rtClient.users.getNotifications returns - so there is no separate push vocabulary. Each item goes through the app it belongs to, or through the hub when the person has no subscription in that app, and never through both.

The browser half is not here. Asking permission, registering with the push service and unsubscribing on sign-out belong to usePushSubscription in @rising-tide/ui/react, which calls these methods itself. Use them directly only for a settings screen that lists or removes devices.

Nothing is sent until the server has a key. The protocol ships with push switched off. While no VAPID key is configured, getVapidPublicKey answers 503 - read that as "push is not offered" and hide the ask. No device can subscribe until then, because a browser needs that key to subscribe at all; devices subscribe once push is switched on.

Every write acts on the caller's own devices. An app-scoped API key may only subscribe devices for its own app.

Methods

rtClient.push.getVapidPublicKey()

The key to pass as applicationServerKey when the browser subscribes. Returns ApiResponse<{ publicKey: string }>, or 503 when push is not offered.

const { data } = await rtClient.push.getVapidPublicKey();
const applicationServerKey = data.publicKey;

rtClient.push.subscribe(input)

Save this device's subscription for the signed-in person in one app. Returns ApiResponse<{ subscription: PushSubscriptionView }>.

const json = browserSubscription.toJSON();

await rtClient.push.subscribe({
  appId: 10,                              // this app's id
  endpoint: json.endpoint!,
  keys: { p256dh: json.keys!.p256dh, auth: json.keys!.auth },
  userAgent: navigator.userAgent,         // optional - shown in the device list
});

Subscribing the same device again updates it in place, and a device another person used moves to the caller. A person holds at most ten devices per app. An eleventh is refused with 409 TOO_MANY_DEVICES; removing a device is the person's choice, so render the message and point them at their device list rather than retrying.


rtClient.push.unsubscribe(target)

Stop pushing to a device. Pass this device's endpoint (sign-out, a toggle), or an id from listSubscriptions to remove another of the caller's devices. Returns ApiResponse<{ removed: boolean }>.

await rtClient.push.unsubscribe({ endpoint: json.endpoint! });
await rtClient.push.unsubscribe({ id: 42 });

rtClient.push.listSubscriptions()

Every device the signed-in person receives push on, across every app. Returns ApiResponse<{ subscriptions: PushSubscriptionView[] }>. The endpoint itself is never returned.

const { data } = await rtClient.push.listSubscriptions();

for (const device of data.subscriptions) {
  // device.id, device.appId, device.appName, device.userAgent, device.lastSuccessAt
}

rtClient.push.recordOpen(deliveryId)

Record that the signed-in person tapped a push and landed here. Returns ApiResponse<{ recorded: boolean }>. Recorded once, and only against the caller's own delivery.

RisingTideProviders calls this itself when the opened URL carries ?via=push&pd=<deliveryId>, then removes both parameters. An app only has to put that marker on the URL its service worker opens.

What a service worker receives

A push event's event.data.json() is a PushPayload:

FieldWhat it is
vThe payload version, currently 1
itemThe NotificationItem itself, so the worker writes the notification in the app's own words
moreFurther items folded into this push; an app may say "and N more"
fallback{ title, body }, where body may be null - an app-neutral sentence. Show it for any item.kind the app does not recognise
badgeThe unread count for navigator.setAppBadge, or null when only a floor is known
deliveryIdPut it on the URL a tap opens, as ?via=push&pd=<deliveryId>

A push must always display something. Safari revokes push permission from a site whose push shows nothing, so a worker that does not recognise a kind shows fallback rather than returning early. An app that declares offers.push in its conformance config is checked for this, for a notificationclick handler, and for calling disable() on sign-out.