Skip to content
This repository was archived by the owner on Jan 4, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,12 @@ export default Ember.Component.extend({
},

@computed
pushNotificationSubscribed: {
isDeniedPermission() {
return Notification.permission === "denied";
},

@computed
isSubscribed: {
set(value) {
const user = this.currentUser;
pushNotificationKeyValueStore.setItem(pushNotificationUserSubscriptionKey(user), value);
Expand All @@ -36,9 +41,14 @@ export default Ember.Component.extend({
}
},

@computed("pushNotificationSubscribed")
instructions(pushNotificationSubscribed) {
if (pushNotificationSubscribed) {
@computed("isDeniedPermission", "isSubscribed")
isAvailable(isDeniedPermission, isSubscribed) {
return !isDeniedPermission && !isSubscribed;
},

@computed("isSubscribed")
instructions(isSubscribed) {
if (isSubscribed) {
return I18n.t("discourse_push_notifications.disable_note");
} else {
return I18n.t("discourse_push_notifications.enable_note");
Expand All @@ -50,13 +60,13 @@ export default Ember.Component.extend({
subscribePushNotification(() => {
desktopNotificationkeyValueStore.setItem('notifications-disabled', 'disabled');
unsubscribeToNotificationAlert(this.messageBus, this.currentUser);
this.set("pushNotificationSubscribed", 'subscribed');
this.set("isSubscribed", 'subscribed');
}, this.siteSettings.vapid_public_key_bytes);
},

unsubscribe() {
unsubscribePushNotification(() => {
this.set("pushNotificationSubscribed", '');
this.set("isSubscribed", '');
});
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { default as computed } from 'ember-addons/ember-computed-decorators';

import {
subscribe as subscribePushNotification,
unsubscribe as unsubscribePushNotification,
isPushNotificationsSupported,
keyValueStore as pushNotificationKeyValueStore,
userSubscriptionKey as pushNotificationUserSubscriptionKey,
userDismissedPrompt as pushNotificationUserDismissedPrompt,
} from 'discourse/plugins/discourse-push-notifications/discourse/lib/push-notifications';

import {
context,
unsubscribe as unsubscribeToNotificationAlert
} from 'discourse/lib/desktop-notifications';

import KeyValueStore from 'discourse/lib/key-value-store';
const desktopNotificationkeyValueStore = new KeyValueStore(context);

export default Ember.Component.extend({
@computed
bannerDismissed: {
set(value) {
const user = this.currentUser;
pushNotificationKeyValueStore.setItem(pushNotificationUserDismissedPrompt(user), value);
return pushNotificationKeyValueStore.getItem(pushNotificationUserDismissedPrompt(user));
},
get() {
return pushNotificationKeyValueStore.getItem(pushNotificationUserDismissedPrompt(Discourse.User.current()));
}
},

@computed
showPushNotificationPrompt() {
return (this.siteSettings.push_notifications_enabled &&
this.siteSettings.push_notifications_prompt &&
Notification.permission !== "denied" &&
isPushNotificationsSupported() && this.currentUser
);
},

@computed
pushNotificationSubscribed: {
set(value) {
const user = this.currentUser;
pushNotificationKeyValueStore.setItem(pushNotificationUserSubscriptionKey(user), value);
return pushNotificationKeyValueStore.getItem(pushNotificationUserSubscriptionKey(user));
},
get() {
return pushNotificationKeyValueStore.getItem(pushNotificationUserSubscriptionKey(Discourse.User.current()));
}
},

actions: {
subscribe() {
subscribePushNotification(() => {
desktopNotificationkeyValueStore.setItem('notifications-disabled', 'disabled');
unsubscribeToNotificationAlert(this.messageBus, this.currentUser);
this.setProperties({bannerDismissed: true, pushNotificationSubscribed: 'subscribed'});
}, this.siteSettings.vapid_public_key_bytes);
},
dismiss() {
this.set("bannerDismissed", true);
}
}
});
12 changes: 8 additions & 4 deletions assets/javascripts/discourse/lib/push-notifications.js.es6
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@ export function userSubscriptionKey(user) {
return `subscribed-${user.get('id')}`;
}

function sendSubscriptionToServer(subscription) {
export function userDismissedPrompt(user) {
return `dismissed-prompt-${user.get('id')}`;
}

function sendSubscriptionToServer(subscription, sendConfirmation) {
ajax('/push_notifications/subscribe', {
type: 'POST',
data: { subscription: subscription.toJSON() }
data: { subscription: subscription.toJSON(), send_confirmation: sendConfirmation }
});
}

Expand Down Expand Up @@ -46,7 +50,7 @@ export function register(user, mobileView, router) {
navigator.serviceWorker.ready.then(serviceWorkerRegistration => {
serviceWorkerRegistration.pushManager.getSubscription().then(subscription => {
if (subscription) {
sendSubscriptionToServer(subscription);
sendSubscriptionToServer(subscription, false);
// Resync localStorage
keyValueStore.setItem(userSubscriptionKey(user), 'subscribed');
}
Expand All @@ -69,7 +73,7 @@ export function subscribe(callback, applicationServerKey) {
userVisibleOnly: true,
applicationServerKey: new Uint8Array(applicationServerKey.split("|")) // eslint-disable-line no-undef
}).then(subscription => {
sendSubscriptionToServer(subscription);
sendSubscriptionToServer(subscription, true);
if (callback) callback();
}).catch(e => Ember.Logger.error(e));
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,14 @@
<label class="control-label">{{i18n 'discourse_push_notifications.title'}}</label>
<div class="controls">
<div>
{{#if pushNotificationSubscribed}}
{{#if isDeniedPermission}}
{{d-button icon="bell-slash-o" label="user.desktop_notifications.perm_denied_btn" disabled="true"}}
{{i18n "user.desktop_notifications.perm_denied_expl"}}
{{/if}}
{{#if isSubscribed}}
{{d-button icon="bell-slash-o" label="discourse_push_notifications.disable" action="unsubscribe"}}
{{else}}
{{/if}}
{{#if isAvailable}}
{{d-button icon="bell-o" label="discourse_push_notifications.enable" action="subscribe"}}
{{/if}}
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{{#if showPushNotificationPrompt}}
{{#unless bannerDismissed}}
{{#unless pushNotificationSubscribed}}
<div class="wrap">
<div class="row">
<div class="consent_banner alert alert-info">
<div class="close" {{action "dismiss"}}><i class="fa fa-times d-icon d-icon-times"></i></div>
{{i18n 'discourse_push_notifications.consent_prompt'}} <a {{action "subscribe"}}>{{i18n 'discourse_push_notifications.consent_enable'}}</a>.
</div>
</div>
</div>
{{/unless}}
{{/unless}}
{{/if}}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{{push-notification-consent}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there an opportunity to re-use {{discourse-banner}} instead?

4 changes: 4 additions & 0 deletions assets/stylesheets/push-notifications.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.push-notification-prompt {
position: relative;
top: 82px;
}
2 changes: 2 additions & 0 deletions config/locales/client.en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ en:
enable: "Enable Push Notifications"
enable_note: "You have to change this setting on every browser you use and this will disable Desktop Notifications."
disable_note: "You have to change this setting on every browser you use."
consent_prompt: "This site needs your permission to"
consent_enable: "enable notifications"
3 changes: 3 additions & 0 deletions config/locales/server.en.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
en:
site_settings:
push_notifications_enabled: Enable push notifications?
push_notifications_prompt: Display user consent prompt.
discourse_push_notifications:
popup:
mentioned: '%{username} mentioned you in "%{topic}" - %{site_title}'
Expand All @@ -10,3 +11,5 @@ en:
posted: '%{username} posted in "%{topic}" - %{site_title}'
private_message: '%{username} sent you a private message in "%{topic}" - %{site_title}'
linked: '%{username} linked to your post from "%{topic}" - %{site_title}'
confirm_title: 'Notifications enabled - %{site_title}'
confirm_body: 'Success! Notifications have been enabled.'
3 changes: 3 additions & 0 deletions config/settings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ plugins:
push_notifications_enabled:
default: false
client: true
push_notifications_prompt:
default: false
client: true
vapid_public_key_bytes:
default: ''
client: true
Expand Down
3 changes: 2 additions & 1 deletion plugin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
enabled_site_setting :push_notifications_enabled

register_service_worker "javascripts/push-service-worker.js"
register_asset "stylesheets/push-notifications.scss"

after_initialize do
module ::DiscoursePushNotifications
Expand Down Expand Up @@ -52,7 +53,7 @@ class DiscoursePushNotifications::PushController < ::ApplicationController
skip_before_action :preload_json

def subscribe
DiscoursePushNotifications::Pusher.subscribe(current_user, push_params)
DiscoursePushNotifications::Pusher.subscribe(current_user, push_params, params[:send_confirmation])
render json: success_json
end

Expand Down
61 changes: 33 additions & 28 deletions services/discourse_push_notifications/pusher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,33 +21,7 @@ def self.push(user, payload)
base_url: Discourse.base_url,
url: payload[:post_url]
}

subject =
if !SiteSetting.contact_email.blank?
"mailto:#{SiteSetting.contact_email}"
elsif !SiteSetting.contact_url.blank?
SiteSetting.contact_url
else
Discourse.base_url
end

begin
response = Webpush.payload_send(
endpoint: subscription["endpoint"],
message: message.to_json,
p256dh: subscription.dig("keys", "p256dh"),
auth: subscription.dig("keys", "auth"),
vapid: {
subject: Discourse.base_url,
public_key: SiteSetting.vapid_public_key,
private_key: SiteSetting.vapid_private_key
}
)
rescue Webpush::InvalidSubscription => e
# Delete the subscription from Redis
updated = true
subscriptions(user).delete(extract_unique_id(subscription))
end
send user, subscription, message
end

user.save_custom_fields(true) if updated
Expand All @@ -65,9 +39,20 @@ def self.clear_subscriptions(user)
user.custom_fields[DiscoursePushNotifications::PLUGIN_NAME] = {}
end

def self.subscribe(user, subscription)
def self.subscribe(user, subscription, send_confirmation)
subscriptions(user)[extract_unique_id(subscription)] = subscription.to_json
user.save_custom_fields(true)
if send_confirmation == "true"
message = {
title: I18n.t("discourse_push_notifications.popup.confirm_title",
site_title: SiteSetting.title),
body: I18n.t("discourse_push_notifications.popup.confirm_body"),
icon: SiteSetting.logo_small_url || SiteSetting.logo_url,
tag: "#{Discourse.current_hostname}-subscription"
}

send user, subscription, message
end
end

def self.unsubscribe(user, subscription)
Expand All @@ -80,5 +65,25 @@ def self.unsubscribe(user, subscription)
def self.extract_unique_id(subscription)
subscription["endpoint"].split("/").last
end

def self.send(user, subscription, message)
begin
response = Webpush.payload_send(
endpoint: subscription["endpoint"],
message: message.to_json,
p256dh: subscription.dig("keys", "p256dh"),
auth: subscription.dig("keys", "auth"),
vapid: {
subject: Discourse.base_url,
public_key: SiteSetting.vapid_public_key,
private_key: SiteSetting.vapid_private_key
}
)
rescue Webpush::InvalidSubscription => e
# Delete the subscription from Redis
updated = true
subscriptions(user).delete(extract_unique_id(subscription))
end
end
end
end