1
0
Fork 0
mirror of https://github.com/binwiederhier/ntfy.git synced 2025-01-16 13:44:58 +01:00
ntfy/web/src/app/SubscriptionManager.js

251 lines
7.7 KiB
JavaScript
Raw Normal View History

import api from "./Api";
import notifier from "./Notifier";
import prefs from "./Prefs";
2023-06-09 20:32:34 +02:00
import db from "./db";
2023-05-23 21:13:01 +02:00
import { topicUrl } from "./utils";
class SubscriptionManager {
2023-06-13 14:02:54 +02:00
constructor(dbImpl) {
this.db = dbImpl;
}
2023-05-23 21:13:01 +02:00
/** All subscriptions, including "new count"; this is a JOIN, see https://dexie.org/docs/API-Reference#joining */
async all() {
const subscriptions = await this.db.subscriptions.toArray();
return Promise.all(
subscriptions.map(async (s) => ({
...s,
new: await this.db.notifications.where({ subscriptionId: s.id, new: 1 }).count(),
}))
2023-05-23 21:13:01 +02:00
);
}
2023-06-11 02:42:02 +02:00
/** List of topics for which Web Push is enabled, excludes internal topics; returns empty list if Web Push is disabled */
async webPushTopics() {
2023-06-08 09:22:56 +02:00
// the Promise.resolve wrapper is not superfluous, without it the live query breaks:
// https://dexie.org/docs/dexie-react-hooks/useLiveQuery()#calling-non-dexie-apis-from-querier
2023-06-11 02:42:02 +02:00
const pushEnabled = await Promise.resolve(notifier.pushEnabled());
if (!pushEnabled) {
2023-06-08 09:22:56 +02:00
return [];
}
const subscriptions = await this.db.subscriptions.where({ mutedUntil: 0, baseUrl: config.base_url }).toArray();
return subscriptions.filter(({ internal }) => !internal).map(({ topic }) => topic);
}
2023-05-23 21:13:01 +02:00
async get(subscriptionId) {
return this.db.subscriptions.get(subscriptionId);
}
async notify(subscriptionId, notification, defaultClickAction) {
const subscription = await this.get(subscriptionId);
if (subscription.mutedUntil === 1) {
return;
}
const priority = notification.priority ?? 3;
if (priority < (await prefs.minPriority())) {
return;
}
await Promise.all([notifier.playSound(), notifier.notify(subscription, notification, defaultClickAction)]);
2023-05-23 21:13:01 +02:00
}
/**
* @param {string} baseUrl
* @param {string} topic
* @param {object} opts
* @param {boolean} opts.internal
* @returns
*/
async add(baseUrl, topic, opts = {}) {
2023-05-23 21:13:01 +02:00
const id = topicUrl(baseUrl, topic);
2023-05-23 21:13:01 +02:00
const existingSubscription = await this.get(id);
if (existingSubscription) {
return existingSubscription;
}
2023-05-23 21:13:01 +02:00
const subscription = {
...opts,
2023-05-23 21:13:01 +02:00
id: topicUrl(baseUrl, topic),
2023-05-24 09:03:28 +02:00
baseUrl,
topic,
2023-05-23 21:13:01 +02:00
mutedUntil: 0,
last: null,
};
await this.db.subscriptions.put(subscription);
2023-05-23 21:13:01 +02:00
return subscription;
}
async syncFromRemote(remoteSubscriptions, remoteReservations) {
2023-05-24 01:29:47 +02:00
console.log(`[SubscriptionManager] Syncing subscriptions from remote`, remoteSubscriptions);
2023-05-23 21:13:01 +02:00
// Add remote subscriptions
const remoteIds = await Promise.all(
remoteSubscriptions.map(async (remote) => {
const reservation = remoteReservations?.find((r) => remote.base_url === config.base_url && remote.topic === r.topic) || null;
const local = await this.add(remote.base_url, remote.topic, {
displayName: remote.display_name, // May be undefined
reservation, // May be null!
});
return local.id;
})
);
2023-05-23 21:13:01 +02:00
// Remove local subscriptions that do not exist remotely
const localSubscriptions = await this.db.subscriptions.toArray();
await Promise.all(
localSubscriptions.map(async (local) => {
const remoteExists = remoteIds.includes(local.id);
if (!local.internal && !remoteExists) {
await this.remove(local);
}
})
);
2023-05-23 21:13:01 +02:00
}
2023-06-11 02:42:02 +02:00
async updateWebPushSubscriptions(presetTopics) {
const topics = presetTopics ?? (await this.webPushTopics());
const browserSubscription = await notifier.getBrowserSubscription();
if (!browserSubscription) {
console.log("[SubscriptionManager] No browser subscription currently exists, so web push was never enabled. Skipping.");
return;
}
if (topics.length > 0) {
await api.updateWebPush(browserSubscription, topics);
} else {
await api.deleteWebPush(browserSubscription);
}
}
2023-05-23 21:13:01 +02:00
async updateState(subscriptionId, state) {
this.db.subscriptions.update(subscriptionId, { state });
2023-05-23 21:13:01 +02:00
}
async remove(subscription) {
await this.db.subscriptions.delete(subscription.id);
await this.db.notifications.where({ subscriptionId: subscription.id }).delete();
2023-05-23 21:13:01 +02:00
}
async first() {
return this.db.subscriptions.toCollection().first(); // May be undefined
2023-05-23 21:13:01 +02:00
}
async getNotifications(subscriptionId) {
// This is quite awkward, but it is the recommended approach as per the Dexie docs.
// It's actually fine, because the reading and filtering is quite fast. The rendering is what's
// killing performance. See https://dexie.org/docs/Collection/Collection.offset()#a-better-paging-approach
return this.db.notifications
2023-05-23 21:13:01 +02:00
.orderBy("time") // Sort by time first
.filter((n) => n.subscriptionId === subscriptionId)
.reverse()
.toArray();
}
async getAllNotifications() {
return this.db.notifications
2023-05-23 21:13:01 +02:00
.orderBy("time") // Efficient, see docs
.reverse()
.toArray();
}
/** Adds notification, or returns false if it already exists */
async addNotification(subscriptionId, notification) {
const exists = await this.db.notifications.get(notification.id);
2023-05-23 21:13:01 +02:00
if (exists) {
return false;
}
try {
// sw.js duplicates this logic, so if you change it here, change it there too
await this.db.notifications.add({
2023-05-24 18:08:59 +02:00
...notification,
subscriptionId,
// New marker (used for bubble indicator); cannot be boolean; Dexie index limitation
new: 1,
}); // FIXME consider put() for double tab
await this.db.subscriptions.update(subscriptionId, {
2023-05-23 21:13:01 +02:00
last: notification.id,
});
} catch (e) {
console.error(`[SubscriptionManager] Error adding notification`, e);
}
return true;
}
/** Adds/replaces notifications, will not throw if they exist */
async addNotifications(subscriptionId, notifications) {
2023-05-24 01:29:47 +02:00
const notificationsWithSubscriptionId = notifications.map((notification) => ({ ...notification, subscriptionId }));
2023-05-23 21:13:01 +02:00
const lastNotificationId = notifications.at(-1).id;
await this.db.notifications.bulkPut(notificationsWithSubscriptionId);
await this.db.subscriptions.update(subscriptionId, {
2023-05-23 21:13:01 +02:00
last: lastNotificationId,
});
}
async updateNotification(notification) {
const exists = await this.db.notifications.get(notification.id);
2023-05-23 21:13:01 +02:00
if (!exists) {
return false;
}
try {
await this.db.notifications.put({ ...notification });
2023-05-23 21:13:01 +02:00
} catch (e) {
console.error(`[SubscriptionManager] Error updating notification`, e);
}
return true;
}
async deleteNotification(notificationId) {
await this.db.notifications.delete(notificationId);
2023-05-23 21:13:01 +02:00
}
async deleteNotifications(subscriptionId) {
await this.db.notifications.where({ subscriptionId }).delete();
2023-05-23 21:13:01 +02:00
}
async markNotificationRead(notificationId) {
await this.db.notifications.where({ id: notificationId }).modify({ new: 0 });
2023-05-23 21:13:01 +02:00
}
async markNotificationsRead(subscriptionId) {
await this.db.notifications.where({ subscriptionId, new: 1 }).modify({ new: 0 });
2023-05-23 21:13:01 +02:00
}
async setMutedUntil(subscriptionId, mutedUntil) {
await this.db.subscriptions.update(subscriptionId, {
2023-05-24 09:03:28 +02:00
mutedUntil,
2023-05-23 21:13:01 +02:00
});
}
2023-05-23 21:13:01 +02:00
async setDisplayName(subscriptionId, displayName) {
await this.db.subscriptions.update(subscriptionId, {
2023-05-24 09:03:28 +02:00
displayName,
2023-05-23 21:13:01 +02:00
});
}
async setReservation(subscriptionId, reservation) {
await this.db.subscriptions.update(subscriptionId, {
2023-05-24 09:03:28 +02:00
reservation,
2023-05-23 21:13:01 +02:00
});
}
async update(subscriptionId, params) {
await this.db.subscriptions.update(subscriptionId, params);
2023-05-23 21:13:01 +02:00
}
async pruneNotifications(thresholdTimestamp) {
await this.db.notifications.where("time").below(thresholdTimestamp).delete();
2023-05-23 21:13:01 +02:00
}
}
2023-06-09 20:32:34 +02:00
export default new SubscriptionManager(db());