Switch to event type

This commit is contained in:
binwiederhier
2026-01-08 20:50:23 -05:00
parent 66ea25c18b
commit 5ad3de2904
11 changed files with 187 additions and 67 deletions

View File

@@ -1,5 +1,6 @@
/* eslint-disable max-classes-per-file */
import { basicAuth, bearerAuth, encodeBase64Url, topicShortUrl, topicUrlWs } from "./utils";
import { EVENT_OPEN, isNotificationEvent } from "./events";
const retryBackoffSeconds = [5, 10, 20, 30, 60, 120];
@@ -48,10 +49,11 @@ class Connection {
console.log(`[Connection, ${this.shortUrl}, ${this.connectionId}] Message received from server: ${event.data}`);
try {
const data = JSON.parse(event.data);
if (data.event === "open") {
if (data.event === EVENT_OPEN) {
return;
}
const relevantAndValid = data.event === "message" && "id" in data && "time" in data && "message" in data;
// Accept message, message_delete, and message_read events
const relevantAndValid = isNotificationEvent(data.event) && "id" in data && "time" in data;
if (!relevantAndValid) {
console.log(`[Connection, ${this.shortUrl}, ${this.connectionId}] Unexpected message. Ignoring.`);
return;

View File

@@ -1,6 +1,7 @@
import api from "./Api";
import prefs from "./Prefs";
import subscriptionManager from "./SubscriptionManager";
import { EVENT_MESSAGE, EVENT_MESSAGE_DELETE } from "./events";
const delayMillis = 2000; // 2 seconds
const intervalMillis = 300000; // 5 minutes
@@ -55,7 +56,7 @@ class Poller {
// Delete all existing notifications for which the latest notification is marked as deleted
const deletedSequenceIds = Object.entries(latestBySequenceId)
.filter(([, notification]) => notification.deleted)
.filter(([, notification]) => notification.event === EVENT_MESSAGE_DELETE)
.map(([sequenceId]) => sequenceId);
if (deletedSequenceIds.length > 0) {
console.log(`[Poller] Deleting notifications with deleted sequence IDs for ${subscription.id}`, deletedSequenceIds);
@@ -65,7 +66,9 @@ class Poller {
}
// Add only the latest notification for each non-deleted sequence
const notificationsToAdd = Object.values(latestBySequenceId).filter((n) => !n.deleted);
const notificationsToAdd = Object
.values(latestBySequenceId)
.filter(n => n.event === EVENT_MESSAGE);
if (notificationsToAdd.length > 0) {
console.log(`[Poller] Adding ${notificationsToAdd.length} notification(s) for ${subscription.id}`);
await subscriptionManager.addNotifications(subscription.id, notificationsToAdd);

View File

@@ -3,6 +3,7 @@ import notifier from "./Notifier";
import prefs from "./Prefs";
import db from "./db";
import { messageWithSequenceId, topicUrl } from "./utils";
import { EVENT_MESSAGE, EVENT_MESSAGE_DELETE, EVENT_MESSAGE_READ } from "./events";
class SubscriptionManager {
constructor(dbImpl) {
@@ -15,7 +16,7 @@ class SubscriptionManager {
return Promise.all(
subscriptions.map(async (s) => ({
...s,
new: await this.db.notifications.where({ subscriptionId: s.id, new: 1 }).count(),
new: await this.db.notifications.where({ subscriptionId: s.id, new: 1 }).count()
}))
);
}
@@ -48,7 +49,7 @@ class SubscriptionManager {
}
async notify(subscriptionId, notification) {
if (notification.deleted) {
if (notification.event !== EVENT_MESSAGE) {
return;
}
const subscription = await this.get(subscriptionId);
@@ -83,7 +84,7 @@ class SubscriptionManager {
baseUrl,
topic,
mutedUntil: 0,
last: null,
last: null
};
await this.db.subscriptions.put(subscription);
@@ -101,7 +102,7 @@ class SubscriptionManager {
const local = await this.add(remote.base_url, remote.topic, {
displayName: remote.display_name, // May be undefined
reservation, // May be null!
reservation // May be null!
});
return local.id;
@@ -174,7 +175,7 @@ class SubscriptionManager {
/** Adds notification, or returns false if it already exists */
async addNotification(subscriptionId, notification) {
const exists = await this.db.notifications.get(notification.id);
if (exists || notification.deleted) {
if (exists || notification.event === EVENT_MESSAGE_DELETE || notification.event === EVENT_MESSAGE_READ) {
return false;
}
try {
@@ -185,13 +186,13 @@ class SubscriptionManager {
await this.db.notifications.add({
...messageWithSequenceId(notification),
subscriptionId,
new: 1, // New marker (used for bubble indicator); cannot be boolean; Dexie index limitation
new: 1 // New marker (used for bubble indicator); cannot be boolean; Dexie index limitation
});
// FIXME consider put() for double tab
// Update subscription last message id (for ?since=... queries)
await this.db.subscriptions.update(subscriptionId, {
last: notification.id,
last: notification.id
});
} catch (e) {
console.error(`[SubscriptionManager] Error adding notification`, e);
@@ -207,7 +208,7 @@ class SubscriptionManager {
const lastNotificationId = notifications.at(-1).id;
await this.db.notifications.bulkPut(notificationsWithSubscriptionId);
await this.db.subscriptions.update(subscriptionId, {
last: lastNotificationId,
last: lastNotificationId
});
}
@@ -250,19 +251,19 @@ class SubscriptionManager {
async setMutedUntil(subscriptionId, mutedUntil) {
await this.db.subscriptions.update(subscriptionId, {
mutedUntil,
mutedUntil
});
}
async setDisplayName(subscriptionId, displayName) {
await this.db.subscriptions.update(subscriptionId, {
displayName,
displayName
});
}
async setReservation(subscriptionId, reservation) {
await this.db.subscriptions.update(subscriptionId, {
reservation,
reservation
});
}

View File

@@ -13,7 +13,7 @@ const createDatabase = (username) => {
db.version(3).stores({
subscriptions: "&id,baseUrl,[baseUrl+mutedUntil]",
notifications: "&id,sequenceId,subscriptionId,time,new,deleted,[subscriptionId+new],[subscriptionId+sequenceId]",
notifications: "&id,sequenceId,subscriptionId,time,new,[subscriptionId+new],[subscriptionId+sequenceId]",
users: "&baseUrl,username",
prefs: "&key"
});

14
web/src/app/events.js Normal file
View File

@@ -0,0 +1,14 @@
// Event types for ntfy messages
// These correspond to the server event types in server/types.go
export const EVENT_OPEN = "open";
export const EVENT_KEEPALIVE = "keepalive";
export const EVENT_MESSAGE = "message";
export const EVENT_MESSAGE_DELETE = "message_delete";
export const EVENT_MESSAGE_READ = "message_read";
export const EVENT_POLL_REQUEST = "poll_request";
// Check if an event is a notification event (message, delete, or read)
export const isNotificationEvent = (event) =>
event === EVENT_MESSAGE || event === EVENT_MESSAGE_DELETE || event === EVENT_MESSAGE_READ;