Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <body> tag, but before you use any Appwrite services:

```html
<script src="https://cdn.jsdelivr.net/npm/appwrite@28.2.0-rc.2"></script>
<script src="https://cdn.jsdelivr.net/npm/appwrite@28.2.0-rc.3"></script>
```

## Getting Started
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
2 changes: 1 addition & 1 deletion src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
};

Expand Down
61 changes: 54 additions & 7 deletions src/services/push.ts
Original file line number Diff line number Diff line change
Expand Up @@ -817,16 +817,21 @@ export class Push {
payload,
qos: packet.qos,
};
const content = notificationContent(message);
const titles = new Set<string>();
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);
Comment thread
hansi-codes[bot] marked this conversation as resolved.
}
}
}
for (const title of titles) {
this.notify(message, title, content);
}
Comment thread
hansi-codes[bot] marked this conversation as resolved.
}

private requestNotificationPermission(): void {
Expand All @@ -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.
}
Expand All @@ -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<string, unknown>)[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));
Expand Down
Loading