From 5dddf717633fdbd588df4b53419640a1abcc0088 Mon Sep 17 00:00:00 2001 From: Thomas Boutell Date: Wed, 12 Nov 2025 08:31:21 -0500 Subject: [PATCH 1/2] longPolling: false option --- CHANGELOG.md | 1 + modules/@apostrophecms/notification/index.js | 30 +++++++++++++++---- .../ui/ui/apos/stores/notification.js | 5 ++-- 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 854b0cfef0..a365e3dbf3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * Translation strings added for the layout- and layout-column-widgets. * When switching locale from the doc editor, ask if the user wants to localize the current document in the target locale or want to start a blank document. +* Introduced a new `longPolling: false` option for the `@apostrophecms/notification` module. This eliminates long-pending requests when logged in, but also slows down the delivery of notifications. The behavior can be tuned further via the `pollingInterval` option, which defaults to `5000` milliseconds. ### Changes diff --git a/modules/@apostrophecms/notification/index.js b/modules/@apostrophecms/notification/index.js index 661937be25..5a442a424b 100644 --- a/modules/@apostrophecms/notification/index.js +++ b/modules/@apostrophecms/notification/index.js @@ -4,6 +4,12 @@ // // ## Options // +// ### `longPolling`: by default, to provide a swift response, ApostropheCMS +// keeps a request for new notifications alive until the long polling +// timeout expires (see below). However, `longPolling: false` can be used +// to give an immediate response, in which case the front end will poll +// the old-fashioned way, respecting the `pollingInterval`. +// // ### `queryInterval`: interval in milliseconds between MongoDB // queries while long polling for notifications. Defaults to 500 // (1/2 second). Set it longer if you prefer fewer queries, however @@ -15,12 +21,22 @@ // Defaults to 10000 (10 seconds) to avoid typical proxy server timeouts. // Until it times out the request will keep making MongoDB queries to // see if any new notifications are available (long polling). +// +// ### `pollingInterval`: when `longPolling` is set to `false`, this +// option determines how often the browser polls for new notifications. +// Not used when `longPolling` is `true` (the default). +// `pollingInterval` defaults to 5000 (5 seconds). const delay = require('bluebird').delay; module.exports = { options: { - alias: 'notification' + alias: 'notification', + longPolling: true, + longPollingTimeout: 10000, + queryInterval: 1000, + // Used only when longPolling is false + pollingInterval: 5000 }, extend: '@apostrophecms/module', async init(self) { @@ -64,7 +80,10 @@ module.exports = { return await attempt(); async function attempt() { - if (Date.now() - start >= (self.options.longPollingTimeout || 10000)) { + if ( + self.options.longPolling && + (Date.now() - start >= self.options.longPollingTimeout) + ) { return { notifications: [], dismissed: [] @@ -75,11 +94,10 @@ module.exports = { modifiedOnOrSince, seenIds }); - if (!notifications.length && !dismissed.length) { + if (self.options.longPolling && !notifications.length && !dismissed.length) { await delay(self.options.queryInterval || 1000); return attempt(); } - return { notifications, dismissed @@ -208,7 +226,9 @@ module.exports = { return { getBrowserData(req) { return { - action: self.action + action: self.action, + longPolling: self.options.longPolling, + pollingInterval: self.options.pollingInterval }; }, // When used server-side, call with `req` as the first argument, diff --git a/modules/@apostrophecms/ui/ui/apos/stores/notification.js b/modules/@apostrophecms/ui/ui/apos/stores/notification.js index cff0cb41c1..ce2c01a749 100644 --- a/modules/@apostrophecms/ui/ui/apos/stores/notification.js +++ b/modules/@apostrophecms/ui/ui/apos/stores/notification.js @@ -107,10 +107,11 @@ export const useNotificationStore = defineStore('notification', () => { return !res.dismissed.some((element) => notif._id === element._id); }); } - // Long polling, we should reconnect promptly, the server + // If using long polling we should reconnect promptly, the server // is responsible for keeping that request open for a reasonable // amount of time if there are no new messages, not us - setTimeout(poll, 50); + const timeout = apos.notification.longPolling ? 50 : apos.notification.pollingInterval; + setTimeout(poll, timeout); } } catch (err) { // eslint-disable-next-line no-console From 69407ab22bbddb29e8ca27424a88eda850963ec8 Mon Sep 17 00:00:00 2001 From: Thomas Boutell Date: Wed, 12 Nov 2025 08:50:17 -0500 Subject: [PATCH 2/2] please the eslint god --- modules/@apostrophecms/notification/index.js | 4 ++-- modules/@apostrophecms/ui/ui/apos/stores/notification.js | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/modules/@apostrophecms/notification/index.js b/modules/@apostrophecms/notification/index.js index 5a442a424b..8460e7dd23 100644 --- a/modules/@apostrophecms/notification/index.js +++ b/modules/@apostrophecms/notification/index.js @@ -81,9 +81,9 @@ module.exports = { async function attempt() { if ( - self.options.longPolling && + self.options.longPolling && (Date.now() - start >= self.options.longPollingTimeout) - ) { + ) { return { notifications: [], dismissed: [] diff --git a/modules/@apostrophecms/ui/ui/apos/stores/notification.js b/modules/@apostrophecms/ui/ui/apos/stores/notification.js index ce2c01a749..29bcd810ff 100644 --- a/modules/@apostrophecms/ui/ui/apos/stores/notification.js +++ b/modules/@apostrophecms/ui/ui/apos/stores/notification.js @@ -110,7 +110,9 @@ export const useNotificationStore = defineStore('notification', () => { // If using long polling we should reconnect promptly, the server // is responsible for keeping that request open for a reasonable // amount of time if there are no new messages, not us - const timeout = apos.notification.longPolling ? 50 : apos.notification.pollingInterval; + const timeout = apos.notification.longPolling + ? 50 + : apos.notification.pollingInterval; setTimeout(poll, timeout); } } catch (err) {