1
0
Fork 0
mirror of https://github.com/binwiederhier/ntfy.git synced 2025-09-08 12:57:55 +02:00

Switch everything to Dexie.js

This commit is contained in:
Philipp Heckel 2022-03-02 16:16:30 -05:00
parent 39f4613719
commit 349872bdb3
16 changed files with 243 additions and 316 deletions

View file

@ -10,11 +10,12 @@ class ConnectionManager {
return;
}
console.log(`[ConnectionManager] Refreshing connections`);
const subscriptionIds = subscriptions.ids();
const subscriptionIds = subscriptions.map(s => s.id);
const deletedIds = Array.from(this.connections.keys()).filter(id => !subscriptionIds.includes(id));
// Create and add new connections
subscriptions.forEach((id, subscription) => {
subscriptions.forEach(subscription => {
const id = subscription.id;
const added = !this.connections.get(id)
if (added) {
const baseUrl = subscription.baseUrl;

View file

@ -1,5 +1,5 @@
import {formatMessage, formatTitleWithFallback, topicShortUrl} from "./utils";
import repository from "./Repository";
import prefs from "./Prefs";
class NotificationManager {
async notify(subscription, notification, onClickFallback) {
@ -7,8 +7,11 @@ class NotificationManager {
if (!shouldNotify) {
return;
}
const shortUrl = topicShortUrl(subscription.baseUrl, subscription.topic);
const message = formatMessage(notification);
const title = formatTitleWithFallback(notification, topicShortUrl(subscription.baseUrl, subscription.topic));
const title = formatTitleWithFallback(notification, shortUrl);
console.log(`[NotificationManager, ${shortUrl}] Displaying notification ${notification.id}: ${message}`);
const n = new Notification(title, {
body: message,
icon: '/static/img/favicon.png'
@ -35,7 +38,7 @@ class NotificationManager {
async shouldNotify(subscription, notification) {
const priority = (notification.priority) ? notification.priority : 3;
const minPriority = await repository.getMinPriority();
const minPriority = await prefs.minPriority();
if (priority < minPriority) {
return false;
}

49
web/src/app/Poller.js Normal file
View file

@ -0,0 +1,49 @@
import db from "./db";
import api from "./Api";
const delayMillis = 3000; // 3 seconds
const intervalMillis = 300000; // 5 minutes
class Poller {
constructor() {
this.timer = null;
}
startWorker() {
if (this.timer !== null) {
return;
}
this.timer = setInterval(() => this.pollAll(), intervalMillis);
setTimeout(() => this.pollAll(), delayMillis);
}
async pollAll() {
console.log(`[Poller] Polling all subscriptions`);
const subscriptions = await db.subscriptions.toArray();
for (const s of subscriptions) {
try {
await this.poll(s);
} catch (e) {
console.log(`[Poller] Error polling ${s.id}`, e);
}
}
}
async poll(subscription) {
console.log(`[Poller] Polling ${subscription.id}`);
const since = subscription.last;
const notifications = await api.poll(subscription.baseUrl, subscription.topic, since);
if (!notifications || notifications.length === 0) {
console.log(`[Poller] No new notifications found for ${subscription.id}`);
return;
}
const notificationsWithSubscriptionId = notifications
.map(notification => ({ ...notification, subscriptionId: subscription.id }));
await db.notifications.bulkPut(notificationsWithSubscriptionId); // FIXME
await db.subscriptions.update(subscription.id, {last: notifications.at(-1).id}); // FIXME
};
}
const poller = new Poller();
export default poller;

33
web/src/app/Prefs.js Normal file
View file

@ -0,0 +1,33 @@
import db from "./db";
class Prefs {
async setSelectedSubscriptionId(selectedSubscriptionId) {
db.prefs.put({key: 'selectedSubscriptionId', value: selectedSubscriptionId});
}
async selectedSubscriptionId() {
const selectedSubscriptionId = await db.prefs.get('selectedSubscriptionId');
return (selectedSubscriptionId) ? selectedSubscriptionId.value : "";
}
async setMinPriority(minPriority) {
db.prefs.put({key: 'minPriority', value: minPriority.toString()});
}
async minPriority() {
const minPriority = await db.prefs.get('minPriority');
return (minPriority) ? Number(minPriority.value) : 1;
}
async setDeleteAfter(deleteAfter) {
db.prefs.put({key:'deleteAfter', value: deleteAfter.toString()});
}
async deleteAfter() {
const deleteAfter = await db.prefs.get('deleteAfter');
return (deleteAfter) ? Number(deleteAfter.value) : 604800; // Default is one week
}
}
const prefs = new Prefs();
export default prefs;

39
web/src/app/Pruner.js Normal file
View file

@ -0,0 +1,39 @@
import db from "./db";
import prefs from "./Prefs";
const delayMillis = 15000; // 15 seconds
const intervalMillis = 1800000; // 30 minutes
class Pruner {
constructor() {
this.timer = null;
}
startWorker() {
if (this.timer !== null) {
return;
}
this.timer = setInterval(() => this.prune(), intervalMillis);
setTimeout(() => this.prune(), delayMillis);
}
async prune() {
const deleteAfterSeconds = await prefs.deleteAfter();
const pruneThresholdTimestamp = Math.round(Date.now()/1000) - deleteAfterSeconds;
if (deleteAfterSeconds === 0) {
console.log(`[Pruner] Pruning is disabled. Skipping.`);
return;
}
console.log(`[Pruner] Pruning notifications older than ${deleteAfterSeconds}s (timestamp ${pruneThresholdTimestamp})`);
try {
await db.notifications
.where("time").below(pruneThresholdTimestamp)
.delete();
} catch (e) {
console.log(`[Pruner] Error pruning old subscriptions`, e);
}
}
}
const pruner = new Pruner();
export default pruner;

View file

@ -1,83 +0,0 @@
import Subscription from "./Subscription";
import Subscriptions from "./Subscriptions";
import db from "./db";
class Repository {
loadSubscriptions() {
console.log(`[Repository] Loading subscriptions from localStorage`);
const subscriptions = new Subscriptions();
subscriptions.loaded = true;
const serialized = localStorage.getItem('subscriptions');
if (serialized === null) {
return subscriptions;
}
try {
JSON.parse(serialized).forEach(s => {
const subscription = new Subscription(s.baseUrl, s.topic);
subscription.addNotifications(s.notifications);
subscription.last = s.last; // Explicitly set, in case old notifications have been deleted
subscriptions.add(subscription);
});
console.log(`[Repository] Loaded ${subscriptions.size()} subscription(s) from localStorage`);
return subscriptions;
} catch (e) {
console.log(`[Repository] Unable to deserialize subscriptions: ${e.message}`);
return subscriptions;
}
}
saveSubscriptions(subscriptions) {
if (!subscriptions.loaded) {
return; // Avoid saving invalid state, triggered by initial useEffect hook
}
console.log(`[Repository] Saving ${subscriptions.size()} subscription(s) to localStorage`);
const serialized = JSON.stringify(subscriptions.map( (id, subscription) => {
return {
baseUrl: subscription.baseUrl,
topic: subscription.topic,
last: subscription.last
}
}));
localStorage.setItem('subscriptions', serialized);
}
async setSelectedSubscriptionId(selectedSubscriptionId) {
console.log(`[Repository] Saving selected subscription ${selectedSubscriptionId}`);
db.prefs.put({key: 'selectedSubscriptionId', value: selectedSubscriptionId});
}
async getSelectedSubscriptionId() {
console.log(`[Repository] Loading selected subscription ID`);
const selectedSubscriptionId = await db.prefs.get('selectedSubscriptionId');
return (selectedSubscriptionId) ? selectedSubscriptionId.value : "";
}
async setMinPriority(minPriority) {
db.prefs.put({key: 'minPriority', value: minPriority.toString()});
}
async getMinPriority() {
const minPriority = await db.prefs.get('minPriority');
return (minPriority) ? Number(minPriority.value) : 1;
}
minPriority() {
return db.prefs.get('minPriority');
}
async setDeleteAfter(deleteAfter) {
db.prefs.put({key:'deleteAfter', value: deleteAfter.toString()});
}
async getDeleteAfter() {
const deleteAfter = await db.prefs.get('deleteAfter');
return (deleteAfter) ? Number(deleteAfter.value) : 604800; // Default is one week
}
deleteAfter() {
return db.prefs.get('deleteAfter');
}
}
const repository = new Repository();
export default repository;

View file

@ -1,33 +0,0 @@
import {topicShortUrl, topicUrl} from './utils';
class Subscription {
constructor(baseUrl, topic) {
this.id = topicUrl(baseUrl, topic);
this.baseUrl = baseUrl;
this.topic = topic;
this.last = null; // Last message ID
}
addNotification(notification) {
if (!notification.event || notification.event !== 'message') {
return false;
}
this.last = notification.id;
return true;
}
addNotifications(notifications) {
notifications.forEach(n => this.addNotification(n));
return this;
}
url() {
return topicUrl(this.baseUrl, this.topic);
}
shortUrl() {
return topicShortUrl(this.baseUrl, this.topic);
}
}
export default Subscription;

View file

@ -1,56 +0,0 @@
class Subscriptions {
constructor() {
this.loaded = false; // FIXME I hate this
this.subscriptions = new Map();
}
add(subscription) {
this.subscriptions.set(subscription.id, subscription);
return this;
}
get(subscriptionId) {
const subscription = this.subscriptions.get(subscriptionId);
return (subscription) ? subscription : null;
}
update(subscription) {
return this.add(subscription);
}
remove(subscriptionId) {
this.subscriptions.delete(subscriptionId);
return this;
}
forEach(cb) {
this.subscriptions.forEach((value, key) => cb(key, value));
}
map(cb) {
return Array.from(this.subscriptions.values())
.map(subscription => cb(subscription.id, subscription));
}
ids() {
return Array.from(this.subscriptions.keys());
}
firstOrNull() {
const first = this.subscriptions.values().next().value;
return (first) ? first : null;
}
size() {
return this.subscriptions.size;
}
clone() {
const c = new Subscriptions();
c.loaded = this.loaded;
c.subscriptions = new Map(this.subscriptions);
return c;
}
}
export default Subscriptions;

View file

@ -9,8 +9,8 @@ import Dexie from 'dexie';
const db = new Dexie('ntfy');
db.version(1).stores({
subscriptions: '&id',
notifications: '&id,subscriptionId',
subscriptions: '&id,baseUrl',
notifications: '&id,subscriptionId,time',
users: '&baseUrl,username',
prefs: '&key'
});