DEV: refactor live notifications setting in user preferences (#28145)

This change is mainly a refactor of the desktop notifications service to improve readability and have standardised values for tracking state for current user in regards to the Notification API and Push API.

Also improves readability when handling push notification jobs, especially in scenarios where the push_notification_time_window_mins site setting is set to 0, which will allow sending push notifications instantly.
This commit is contained in:
David Battersby 2024-08-02 17:25:15 +04:00 committed by GitHub
parent ec46487870
commit 6ec8728ebf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 69 additions and 63 deletions

View File

@ -61,8 +61,7 @@ function init(messageBus) {
} catch (e) {
// eslint-disable-next-line no-console
console.warn(
"Unexpected error, Notification is defined on window but not a responding correctly " +
e
"Notification is defined on window but is not responding correctly " + e
);
}
@ -136,7 +135,7 @@ function canUserReceiveNotifications(user) {
return false;
}
if (keyValueStore.getItem("notifications-disabled")) {
if (keyValueStore.getItem("notifications-disabled", "disabled")) {
return false;
}
@ -168,7 +167,7 @@ async function onNotification(data, siteSettings, user, appEvents) {
siteSettings.site_logo_small_url || siteSettings.site_logo_url;
const notificationTag =
"discourse-notification-" + siteSettings.title + "-" + data.topic_id;
"discourse-notification-" + siteSettings.title + "-" + (data.topic_id || 0);
await requestPermission();

View File

@ -18,6 +18,7 @@ import {
const keyValueStore = new KeyValueStore(context);
const DISABLED = "disabled";
const ENABLED = "enabled";
const SUBSCRIBED = "subscribed";
@disableImplicitInjections
export default class DesktopNotificationsService extends Service {
@ -25,17 +26,19 @@ export default class DesktopNotificationsService extends Service {
@service site;
@service siteSettings;
@tracked notificationsDisabled;
@tracked isEnabledPush;
@tracked isEnabledBrowser = false;
@tracked isEnabledPush = false;
constructor() {
super(...arguments);
this.notificationsDisabled =
keyValueStore.getItem("notifications-disabled") === DISABLED;
this.isEnabledBrowser = this.isGrantedPermission
? keyValueStore.getItem("notifications-disabled") === ENABLED
: false;
this.isEnabledPush = this.currentUser
? pushNotificationKeyValueStore.getItem(
pushNotificationUserSubscriptionKey(this.currentUser)
)
) === SUBSCRIBED
: false;
}
@ -47,11 +50,6 @@ export default class DesktopNotificationsService extends Service {
return this.isNotSupported ? "" : Notification.permission;
}
setNotificationsDisabled(value) {
keyValueStore.setItem("notifications-disabled", value);
this.notificationsDisabled = value === DISABLED;
}
get isDeniedPermission() {
if (this.isNotSupported) {
return false;
@ -68,30 +66,8 @@ export default class DesktopNotificationsService extends Service {
return this.notificationsPermission === "granted";
}
get isEnabledDesktop() {
if (this.isGrantedPermission) {
return !this.notificationsDisabled;
}
return false;
}
setIsEnabledPush(value) {
const user = this.currentUser;
if (!user) {
return false;
}
pushNotificationKeyValueStore.setItem(
pushNotificationUserSubscriptionKey(user),
value
);
this.isEnabledPush = pushNotificationKeyValueStore.getItem(
pushNotificationUserSubscriptionKey(user)
);
}
get isEnabled() {
return this.isEnabledDesktop || this.isEnabledPush;
return this.isEnabledPush || this.isEnabledBrowser;
}
get isSubscribed() {
@ -99,11 +75,9 @@ export default class DesktopNotificationsService extends Service {
return false;
}
if (this.isPushNotificationsPreferred) {
return this.isEnabledPush === "subscribed";
} else {
return !this.notificationsDisabled;
}
return this.isPushNotificationsPreferred
? this.isEnabledPush
: this.isEnabledBrowser;
}
get isPushNotificationsPreferred() {
@ -114,14 +88,36 @@ export default class DesktopNotificationsService extends Service {
);
}
setIsEnabledBrowser(value) {
const status = value ? ENABLED : DISABLED;
keyValueStore.setItem("notifications-disabled", status);
this.isEnabledBrowser = value;
}
setIsEnabledPush(value) {
const user = this.currentUser;
const status = value ? SUBSCRIBED : value;
if (!user) {
return false;
}
pushNotificationKeyValueStore.setItem(
pushNotificationUserSubscriptionKey(user),
status
);
this.isEnabledPush = value;
}
@action
async disable() {
if (this.isEnabledDesktop) {
this.setNotificationsDisabled(DISABLED);
if (this.isEnabledBrowser) {
this.setIsEnabledBrowser(false);
}
if (this.isEnabledPush) {
await unsubscribePushNotification(this.currentUser, () => {
this.setIsEnabledPush("");
this.setIsEnabledPush(false);
});
}
@ -129,16 +125,22 @@ export default class DesktopNotificationsService extends Service {
}
@action
enable() {
async enable() {
if (this.isPushNotificationsPreferred) {
return subscribePushNotification(() => {
this.setIsEnabledPush("subscribed");
await subscribePushNotification(() => {
this.setIsEnabledPush(true);
}, this.siteSettings.vapid_public_key_bytes);
return true;
} else {
this.setNotificationsDisabled(ENABLED);
return Notification.requestPermission((permission) => {
await Notification.requestPermission((permission) => {
confirmNotification(this.siteSettings);
return permission === "granted";
if (permission === "granted") {
this.setIsEnabledBrowser(true);
return true;
} else {
return false;
}
});
}
}

View File

@ -4,9 +4,8 @@ module Jobs
class SendPushNotification < ::Jobs::Base
def execute(args)
user = User.find_by(id: args[:user_id])
if !user || user.seen_since?(SiteSetting.push_notification_time_window_mins.minutes.ago)
return
end
push_window = SiteSetting.push_notification_time_window_mins
return if !user || (push_window > 0 && user.seen_since?(push_window.minutes.ago))
PushNotificationPusher.push(user, args[:payload])
end

View File

@ -84,7 +84,8 @@ class PostAlerter
end
if user.push_subscriptions.exists?
if user.seen_since?(SiteSetting.push_notification_time_window_mins.minutes.ago)
push_window = SiteSetting.push_notification_time_window_mins
if push_window > 0 && user.seen_since?(push_window.minutes.ago)
delay =
(SiteSetting.push_notification_time_window_mins - (Time.now - user.last_seen_at) / 60)
Jobs.enqueue_in(delay.minutes, :send_push_notification, user_id: user.id, payload: payload)

View File

@ -27,7 +27,6 @@ class PushNotificationPusher
tag: payload[:tag] || "#{Discourse.current_hostname}-#{payload[:topic_id]}",
base_url: Discourse.base_url,
url: payload[:post_url],
hide_when_active: true,
}
subscriptions(user).each { |subscription| send_notification(user, subscription, message) }

View File

@ -10,23 +10,29 @@ RSpec.describe Jobs::SendPushNotification do
SiteSetting.push_notification_time_window_mins = 10
end
context "with active online user" do
it "does not send push notification" do
user.update!(last_seen_at: 5.minutes.ago)
context "with valid user" do
it "does not send push notification when user is online" do
user.update!(last_seen_at: 2.minutes.ago)
PushNotificationPusher.expects(:push).with(user, payload).never
Jobs::SendPushNotification.new.execute(user_id: user, payload: payload)
end
end
context "with inactive offline user" do
it "sends push notification" do
user.update!(last_seen_at: 40.minutes.ago)
it "sends push notification when user is offline" do
user.update!(last_seen_at: 20.minutes.ago)
PushNotificationPusher.expects(:push).with(user, payload)
Jobs::SendPushNotification.new.execute(user_id: user, payload: payload)
end
end
context "with invalid user" do
it "does not send push notification" do
PushNotificationPusher.expects(:push).with(user, payload).never
Jobs::SendPushNotification.new.execute(user_id: -999, payload: payload)
end
end
end