diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3d5e295..9ee9ccc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,12 @@
# Change Log
+## 28.2.0-rc.3
+
+* Added: background push notifications render the server `notification` title, body, and image
+* Added: notification `data` now carries `topic` and `payload`
+* Fixed: a server-sent title dedupes to one notification per topic instead of one per subscription
+* Fixed: malformed push payloads no longer break notification rendering
+
## 28.2.0-rc.2
* Added: `Push` service for MQTT realtime push messages
diff --git a/README.md b/README.md
index cac2883..a72dfb6 100644
--- a/README.md
+++ b/README.md
@@ -33,7 +33,7 @@ import { Client, Account } from 'appwrite';
To install with a CDN (content delivery network) add the following scripts to the bottom of your
tag, but before you use any Appwrite services:
```html
-
+
```
## Getting Started
diff --git a/package-lock.json b/package-lock.json
index 30413c1..80ed902 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "appwrite",
- "version": "28.2.0-rc.2",
+ "version": "28.2.0-rc.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "appwrite",
- "version": "28.2.0-rc.2",
+ "version": "28.2.0-rc.3",
"license": "BSD-3-Clause",
"dependencies": {
"buffer": "6.0.3",
diff --git a/package.json b/package.json
index 1b338a8..ced404a 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
"name": "appwrite",
"homepage": "https://appwrite.io/support",
"description": "Appwrite is an open-source self-hosted backend server that abstracts and simplifies complex and repetitive development tasks behind a very simple REST API",
- "version": "28.2.0-rc.2",
+ "version": "28.2.0-rc.3",
"license": "BSD-3-Clause",
"main": "dist/cjs/sdk.js",
"exports": {
diff --git a/src/client.ts b/src/client.ts
index 7cb7557..6ed4147 100644
--- a/src/client.ts
+++ b/src/client.ts
@@ -401,7 +401,7 @@ class Client {
'x-sdk-name': 'Web',
'x-sdk-platform': 'client',
'x-sdk-language': 'web',
- 'x-sdk-version': '28.2.0-rc.2',
+ 'x-sdk-version': '28.2.0-rc.3',
'X-Appwrite-Response-Format': '2.3.0',
};
diff --git a/src/services/push.ts b/src/services/push.ts
index 5b2162d..fb731dc 100644
--- a/src/services/push.ts
+++ b/src/services/push.ts
@@ -817,16 +817,21 @@ export class Push {
payload,
qos: packet.qos,
};
+ const content = notificationContent(message);
+ const titles = new Set();
for (const sub of this.subscriptions.values()) {
if (matches(sub.topic, message.topic)) {
await sub.callback(message);
- // Notification is per-subscription: only subs that opted in post one,
- // each with its own title.
+ // Notification is per-subscription: only subs that opted in post one, each
+ // with its own title. A title the server sent replaces theirs, so one posts.
if (sub.background) {
- this.notify(message, sub.title);
+ titles.add(content.title ?? sub.title ?? message.topic);
}
}
}
+ for (const title of titles) {
+ this.notify(message, title, content);
+ }
}
private requestNotificationPermission(): void {
@@ -841,17 +846,26 @@ export class Push {
}
/** Post a browser notification for a message a background subscription matched. */
- private notify(message: PushMessage, title?: string): void {
+ private notify(
+ message: PushMessage,
+ title: string,
+ content: NotificationContent,
+ ): void {
if (
typeof Notification === 'undefined' ||
Notification.permission !== 'granted'
) {
return;
}
+ const raw = !content.present;
+ // `image` is not in the DOM typings, but Chromium-based browsers show it.
+ const options: NotificationOptions & { image?: string } = {
+ body: content.body ?? (raw ? message.data : undefined),
+ image: content.image,
+ data: { topic: message.topic, payload: message.data },
+ };
try {
- new Notification(title ?? message.topic, {
- body: message.data,
- });
+ new Notification(title, options);
} catch {
// Some browsers only allow notifications from a service worker; ignore.
}
@@ -864,6 +878,39 @@ class SupersededError extends Error {
}
}
+/** What a message's `notification` block asks a background notification to show. */
+interface NotificationContent {
+ /** Whether the message has a `notification` block at all. */
+ present: boolean;
+ title?: string;
+ body?: string;
+ image?: string;
+}
+
+/** The server's `notification` block in a message; empty when the payload has none or is not JSON. */
+function notificationContent(message: PushMessage): NotificationContent {
+ let notification: unknown;
+ try {
+ notification = (JSON.parse(message.data) as { notification?: unknown })
+ ?.notification;
+ } catch {
+ return { present: false };
+ }
+ if (typeof notification !== 'object' || notification === null) {
+ return { present: false };
+ }
+ const field = (name: string): string | undefined => {
+ const value = (notification as Record)[name];
+ return typeof value === 'string' && value !== '' ? value : undefined;
+ };
+ return {
+ present: true,
+ title: field('title'),
+ body: field('body'),
+ image: field('image'),
+ };
+}
+
/** Normalize anything thrown or passed to an error handler into an `Error`. */
function toError(error: unknown): Error {
return error instanceof Error ? error : new Error(String(error));