diff --git a/design/notifications.handlebars b/design/notifications.handlebars index 477bc51..72401c7 100644 --- a/design/notifications.handlebars +++ b/design/notifications.handlebars @@ -42,4 +42,4 @@ {{/if}}{{/each}} app.pollForPosts(); - \ No newline at end of file + diff --git a/design/partials/note.handlebars b/design/partials/note.handlebars index 27139b2..7b17205 100644 --- a/design/partials/note.handlebars +++ b/design/partials/note.handlebars @@ -25,6 +25,7 @@ {{/each}} diff --git a/lib/UserEvent.js b/lib/UserEvent.js new file mode 100644 index 0000000..cf5a07d --- /dev/null +++ b/lib/UserEvent.js @@ -0,0 +1,34 @@ +import { once, EventEmitter } from 'events'; +import debug from 'debug'; + +const logger = debug('ono:event'); + +/** + * UserEvent - a class for handling real-time event updates + */ +export class UserEventClient { + + constructor() { + this.emitter = new EventEmitter(); + this.ac = new AbortController(); + } + + sendEvent(e, data) { + logger('SENDING EVENT ' + data); + this.emitter.emit('poll', data); + } + + abort() { + this.ac.abort(); + this.ac = new AbortController(); + } + + async waitForEvent() { + const [data] = await once(this.emitter, 'poll', { signal: this.ac.signal }); + logger('RECEIVED EVENT ' + data); + } + +} + +export const UserEvent = new UserEventClient(); + diff --git a/lib/account.js b/lib/account.js index c3fff64..3c49358 100644 --- a/lib/account.js +++ b/lib/account.js @@ -31,6 +31,9 @@ import { getActivity, createActivity } from './notes.js'; +import { + UserEvent +} from '../lib/UserEvent.js'; import debug from 'debug'; import MarkdownIt from 'markdown-it'; @@ -116,6 +119,9 @@ export const acceptDM = (dm, inboxUser) => { writeInboxIndex(inboxIndex); + if (dm.attributedTo !== ActivityPub.actor.id) { + UserEvent.sendEvent('dm'); + } } export const isMyPost = (activity) => { @@ -228,6 +234,7 @@ export const addNotification = (notification) => { notification: notification, }); writeNotifications(notifications); + UserEvent.sendEvent('notification'); } const writeNotifications = (notifications) => { diff --git a/public/app.js b/public/app.js index 57635ad..54c7ec1 100644 --- a/public/app.js +++ b/public/app.js @@ -38,6 +38,7 @@ const getCookie = (name) => { } const app = { + firstPoll: true, newPosts: 0, newNotifications: 0, latestPost: (date) => { @@ -102,12 +103,14 @@ const app = { }, pollForPosts: () => { - fetch('/private/poll','get').then((json) => { + fetch('/private/poll' + (app.firstPoll ? '?nowait=1' : ''),'get').then((json) => { + app.firstPoll = false; const res = JSON.parse(json); app.alertNewPosts(res); - setTimeout(() => app.pollForPosts(), 30000); // poll every 5 seconds + setTimeout(() => app.pollForPosts(), 1000); // poll every 1 seconds, endpoint will stall until event occurs }).catch((err) => { console.error(err); + setTimeout(() => app.pollForPosts(), 1000); // poll every 1 seconds, endpoint will stall until event occurs }); }, toggleBoost: (el, postId) => { @@ -265,4 +268,4 @@ const app = { } return false; } -} \ No newline at end of file +} diff --git a/routes/admin.js b/routes/admin.js index 167bb0e..5e88058 100644 --- a/routes/admin.js +++ b/routes/admin.js @@ -31,6 +31,9 @@ import { import { ActivityPub } from '../lib/ActivityPub.js'; +import { + UserEvent +} from '../lib/UserEvent.js'; const logger = debug('ono:admin'); router.get('/index', async (req, res) => { @@ -38,16 +41,26 @@ router.get('/index', async (req, res) => { }); router.get('/poll', async (req, res) => { - + if (!req.query.nowait) { + req.on('close', function (err){ + UserEvent.abort(); + return; + }); + try { + await UserEvent.waitForEvent(); + } catch(e) { + // we got aborted + } + } const sincePosts = new Date(req.cookies.latestPost).getTime(); - const sinceNotifications = parseInt(req.cookies.latestNotification); + const sinceNotifications = parseInt(req.cookies.latestNotification);//.filter((n) => {n.}); + // notification mechanism used to indicate there are unread posts, but they shouldn't appear in notifications tab const notifications = getNotifications().filter((n) => n.time > sinceNotifications); const inboxIndex = getInboxIndex(); const unreadDM = Object.keys(inboxIndex).filter((k) => { return !inboxIndex[k].lastRead || inboxIndex[k].lastRead < inboxIndex[k].latest; })?.length || 0; - const { activitystream } = await getActivitySince(sincePosts, true); @@ -545,4 +558,4 @@ router.post('/boost', async (req, res) => { }); } writeBoosts(boosts); -}); \ No newline at end of file +}); diff --git a/routes/inbox.js b/routes/inbox.js index 8cea8a4..e8aa82d 100644 --- a/routes/inbox.js +++ b/routes/inbox.js @@ -29,10 +29,11 @@ import debug from 'debug'; import { isIndexed } from '../lib/storage.js'; +import { + UserEvent +} from '../lib/UserEvent.js'; const logger = debug('ono:inbox'); - - router.post('/', async (req, res) => { const incomingRequest = req.body; @@ -154,6 +155,7 @@ router.post('/', async (req, res) => { } else if (!incomingRequest.object.inReplyTo) { // this is a NEW post - most likely from a follower await createActivity(incomingRequest.object); + UserEvent.sendEvent('msg'); } else { // this is a reply // from a following @@ -180,4 +182,4 @@ router.post('/', async (req, res) => { logger('Unknown request format:', incomingRequest); } return res.status(200).send(); -}); \ No newline at end of file +});