Replace Pusher Beams with Rpush PN delivery ✂️ ✂️ (#13588)
* Remove feature flag * Remove Pusher Beams implementation * Remove Pusher Beams require from initializer * Adds consumer apps to sidebar + consider :admin_restructure * Fix routes + PN target URL * Remove old file that sneaked in * Adds docs * docs tweaks * Apply suggestions from code review Co-authored-by: rhymes <rhymes@hey.com> Co-authored-by: rhymes <rhymes@hey.com>
This commit is contained in:
parent
355d510f88
commit
121603a44b
18 changed files with 46 additions and 214 deletions
|
|
@ -120,8 +120,6 @@ PUSHER_APP_ID=
|
|||
PUSHER_CLUSTER=
|
||||
PUSHER_KEY=
|
||||
PUSHER_SECRET=
|
||||
PUSHER_BEAMS_ID=
|
||||
PUSHER_BEAMS_KEY=
|
||||
|
||||
# Google recaptcha (Optional)
|
||||
# (https://developers.google.com/recaptcha/intro)
|
||||
|
|
|
|||
1
Gemfile
1
Gemfile
|
|
@ -70,7 +70,6 @@ gem "pg_search", "~> 2.3.5" # PgSearch builds Active Record named scopes that ta
|
|||
gem "puma", "~> 5.2.2" # Puma is a simple, fast, threaded, and highly concurrent HTTP 1.1 server
|
||||
gem "pundit", "~> 2.1" # Object oriented authorization for Rails applications
|
||||
gem "pusher", "~> 2.0" # Ruby library for Pusher Channels HTTP API
|
||||
gem "pusher-push-notifications", "~> 2.0" # Pusher Push Notifications Ruby server SDK
|
||||
gem "rack-attack", "~> 6.5.0" # Used to throttle requests to prevent brute force attacks
|
||||
gem "rack-cors", "~> 1.1" # Middleware that will make Rack-based apps CORS compatible
|
||||
gem "rack-timeout", "~> 0.6" # Rack middleware which aborts requests that have been running for longer than a specified timeout
|
||||
|
|
|
|||
|
|
@ -526,9 +526,6 @@ GEM
|
|||
httpclient (~> 2.8)
|
||||
multi_json (~> 1.15)
|
||||
pusher-signature (~> 0.1.8)
|
||||
pusher-push-notifications (2.0.1)
|
||||
jwt (~> 2.1, >= 2.1.0)
|
||||
rest-client (~> 2.0, >= 2.0.2)
|
||||
pusher-signature (0.1.8)
|
||||
raabro (1.4.0)
|
||||
racc (1.5.2)
|
||||
|
|
@ -933,7 +930,6 @@ DEPENDENCIES
|
|||
pundit (~> 2.1)
|
||||
pundit-matchers (~> 1.6)
|
||||
pusher (~> 2.0)
|
||||
pusher-push-notifications (~> 2.0)
|
||||
rack-attack (~> 6.5.0)
|
||||
rack-cors (~> 1.1)
|
||||
rack-host-redirect (~> 1.3)
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ class AdminMenu
|
|||
|
||||
scope :apps, "palette-line", [
|
||||
item(name: "chat channels"),
|
||||
item(name: "consumer apps", controller: "consumer_apps"),
|
||||
item(name: "events"),
|
||||
item(name: "listings"),
|
||||
item(name: "welcome"),
|
||||
|
|
|
|||
|
|
@ -34,27 +34,17 @@ module Notifications
|
|||
)
|
||||
end
|
||||
|
||||
# Send PNs using Rpush - respecting users' notificaton delivery settings
|
||||
targets = User.where(id: user_ids, mobile_comment_notifications: true).ids
|
||||
|
||||
# Pusher Beams uses named Pub/Sub channels instead of raw user_ids
|
||||
target_channels = targets.map { |id| "user-notifications-#{id}" }
|
||||
# Sends the push notification to Pusher Beams channels.
|
||||
# Batch is in place to respect Pusher 100 channel limit.
|
||||
target_channels.each_slice(100) { |batch| send_push_notifications(batch) }
|
||||
|
||||
if FeatureFlag.enabled?(:mobile_notifications)
|
||||
# Send PNs using Rpush
|
||||
url_path = Rails.application.routes.url_helpers.notifications_path(:comments)
|
||||
PushNotifications::Send.call(
|
||||
user_ids: targets,
|
||||
title: "@#{comment.user.username}",
|
||||
body: "Re: #{comment.parent_or_root_article.title.strip}",
|
||||
payload: {
|
||||
url: URL.url(url_path),
|
||||
type: "new comment"
|
||||
},
|
||||
)
|
||||
end
|
||||
PushNotifications::Send.call(
|
||||
user_ids: targets,
|
||||
title: "@#{comment.user.username}",
|
||||
body: "Re: #{comment.parent_or_root_article.title.strip}",
|
||||
payload: {
|
||||
url: URL.url(comment.path),
|
||||
type: "new comment"
|
||||
},
|
||||
)
|
||||
|
||||
return unless comment.commentable.organization_id
|
||||
|
||||
|
|
@ -97,40 +87,6 @@ module Notifications
|
|||
|
||||
user_ids_for("only_author_comments")
|
||||
end
|
||||
|
||||
def send_push_notifications(channels)
|
||||
return unless ApplicationConfig["PUSHER_BEAMS_KEY"] && ApplicationConfig["PUSHER_BEAMS_KEY"].size == 64
|
||||
|
||||
Pusher::PushNotifications.publish_to_interests(
|
||||
interests: channels,
|
||||
payload: push_notification_payload,
|
||||
)
|
||||
end
|
||||
|
||||
def push_notification_payload
|
||||
title = "@#{comment.user.username}"
|
||||
subtitle = "re: #{comment.parent_or_root_article.title.strip}"
|
||||
data_payload = { url: URL.url("/notifications/comments") }
|
||||
{
|
||||
apns: {
|
||||
aps: {
|
||||
alert: {
|
||||
title: title,
|
||||
subtitle: subtitle,
|
||||
body: CGI.unescapeHTML(comment.title.strip)
|
||||
}
|
||||
},
|
||||
data: data_payload
|
||||
},
|
||||
fcm: {
|
||||
notification: {
|
||||
title: title,
|
||||
body: subtitle
|
||||
},
|
||||
data: data_payload
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -12,8 +12,6 @@ module PushNotifications
|
|||
end
|
||||
|
||||
def call
|
||||
return unless FeatureFlag.enabled?(:mobile_notifications)
|
||||
|
||||
Device.where(user_id: @user_ids).find_each do |device|
|
||||
device.create_notification(@title, @body, @payload)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,7 +4,10 @@
|
|||
<h1 class="crayons-title mb-6">Consumer Apps</h1>
|
||||
|
||||
<p>
|
||||
The following are apps that will be able to receive Push Notifications from <%= SiteConfig.community_name %>.
|
||||
Consumer apps are standalone mobile apps that users can use to browse <%= SiteConfig.community_name %> as an alternative to web browsers.
|
||||
</p>
|
||||
<p class="mt-3">
|
||||
If the app is listed as <strong>operational</strong>, it means <%= SiteConfig.community_name %> will be able to deliver push notifications directly to users' devices.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
require "pusher/push_notifications"
|
||||
|
||||
if ApplicationConfig["PUSHER_APP_ID"].present?
|
||||
Pusher.app_id = ApplicationConfig["PUSHER_APP_ID"]
|
||||
Pusher.key = ApplicationConfig["PUSHER_KEY"]
|
||||
|
|
@ -7,9 +5,4 @@ if ApplicationConfig["PUSHER_APP_ID"].present?
|
|||
Pusher.cluster = ApplicationConfig["PUSHER_CLUSTER"]
|
||||
Pusher.logger = Rails.logger
|
||||
Pusher.encrypted = true
|
||||
|
||||
Pusher::PushNotifications.configure do |config|
|
||||
config.instance_id = ApplicationConfig["PUSHER_BEAMS_ID"]
|
||||
config.secret_key = ApplicationConfig["PUSHER_BEAMS_KEY"]
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -134,10 +134,8 @@ Rails.application.routes.draw do
|
|||
end
|
||||
resources :comment_mutes, only: %i[update]
|
||||
resources :users, only: %i[index], defaults: { format: :json } do # internal API
|
||||
constraints(-> { FeatureFlag.enabled?(:mobile_notifications) }) do
|
||||
collection do
|
||||
resources :devices, only: %i[create destroy]
|
||||
end
|
||||
collection do
|
||||
resources :devices, only: %i[create destroy]
|
||||
end
|
||||
end
|
||||
resources :users, only: %i[update]
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ namespace :admin do
|
|||
resources :organization_memberships, only: %i[update destroy create]
|
||||
resources :permissions, only: %i[index]
|
||||
resources :reactions, only: [:update]
|
||||
resources :consumer_apps, only: %i[index new create edit update destroy]
|
||||
namespace :settings do
|
||||
resources :authentications, only: [:create]
|
||||
resources :campaigns, only: [:create]
|
||||
|
|
@ -123,6 +122,7 @@ namespace :admin do
|
|||
delete :remove_user
|
||||
end
|
||||
end
|
||||
resources :consumer_apps, only: %i[index new create edit update destroy]
|
||||
resources :events, only: %i[index create update new edit]
|
||||
resources :listings, only: %i[index edit update destroy]
|
||||
resources :listing_categories, only: %i[index edit update new create
|
||||
|
|
|
|||
27
docs/backend/push-notifications.md
Normal file
27
docs/backend/push-notifications.md
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
title: Push Notifications
|
||||
---
|
||||
|
||||
# Push Notifications Delivery
|
||||
|
||||
Forem instances rely on [Rpush](https://github.com/rpush/rpush) to deliver push
|
||||
notifications. This decision was heavily influenced by the desire to
|
||||
provide as much flexibility as possible to Creators. In order to do this, Forem
|
||||
instances can register and configure a `ConsumerApp`.
|
||||
|
||||
These consumer apps represent mobile applications that users use to browse and consume content
|
||||
on a Forem. Authenticated users of a specific Forem instance can register a `Device`
|
||||
associated to a `ConsumerApp`. With these pieces we are able to deliver
|
||||
push notifications to users devices.
|
||||
|
||||
The `ConsumerApp` configuration page can be found at
|
||||
`/admin/apps/consumer_apps`. The official Forem apps are supported by default
|
||||
and require their secret credential to be provided via ENV variable.
|
||||
|
||||
## Rpush Implementation
|
||||
|
||||
We use Rpush's `rpush-redis` implementation (read
|
||||
[this thread](https://github.com/forem/forem/pull/12419/files#r564660917) for
|
||||
the reasons why), hence all `Rpush` models are persisted in Redis. More
|
||||
details about how this works
|
||||
[here](https://github.com/rpush/rpush/wiki/Using-Redis).
|
||||
|
|
@ -40,68 +40,3 @@ you'll need to provide those keys to the Rails application.
|
|||

|
||||
|
||||
5. Done.
|
||||
|
||||
## Mobile Push Notifications
|
||||
|
||||
These steps are required only when working with the native Apps. In order to
|
||||
setup Push Notifications to mobile devices you need to create a Pusher Beams
|
||||
instance and retrieve its credentials
|
||||
|
||||
1. [Sign up](https://dashboard.pusher.com/accounts/sign_up) or
|
||||
[sign in](https://dashboard.pusher.com/) to your Pusher account.
|
||||
|
||||
2. Once signed in, go in the "BEAMS" section on the left sidebar and fill in the
|
||||
prompt to create a new Pusher Beams instance.
|
||||
|
||||

|
||||
|
||||
3. In your new Pusher Beams instance, click the "Credentials" tab.
|
||||
|
||||

|
||||
|
||||
4. Change your keys accordingly (name of Pusher key -> name of our application
|
||||
key):
|
||||
|
||||
```text
|
||||
Instance ID -> PUSHER_BEAMS_ID
|
||||
Instance Key -> PUSHER_BEAMS_KEY
|
||||
```
|
||||
|
||||
5. Done. You now have your server configured to use Pusher Beams.
|
||||
|
||||
However, in order to send Push Notifications to devices you'll need to do some
|
||||
platform specific configuration as well.
|
||||
|
||||
### Firebase for Android Push Notifications
|
||||
|
||||
1. Sign up or Sign in to [Firebase](https://firebase.google.com/)
|
||||
|
||||
2. Add a new project. Google Analytics doesn't need to be configured for this
|
||||
Push Notifications to work.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
3. Add Android support for your iOS app
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
4. Download the `google-services.json` file (to be used in the Android app)
|
||||
|
||||

|
||||
|
||||
5. Finish off the next steps (not required at this moment). Now head over to
|
||||
your Firebase project's settings page and copy the Server key from the "Cloud
|
||||
Messaging" tab
|
||||
|
||||

|
||||
|
||||
6. Now back in your Pusher Beams page account add the Server key from the
|
||||
previous step under the "Settings" tab
|
||||
|
||||

|
||||
|
||||
7. Done.
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ items:
|
|||
- notification.md
|
||||
- scheduled-jobs.md
|
||||
- metrics.md
|
||||
- push-notifications.md
|
||||
- tracking.md
|
||||
- service-objects.md
|
||||
---
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ RSpec.describe "Devices", type: :request do
|
|||
let(:consumer_app) { create(:consumer_app) }
|
||||
|
||||
before do
|
||||
allow(FeatureFlag).to receive(:enabled?).with(:mobile_notifications).and_return(true)
|
||||
sign_in user
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1,42 +0,0 @@
|
|||
require "rails_helper"
|
||||
|
||||
RSpec.describe "User devices routes", type: :routing do
|
||||
it "renders the user devices routes if the mobile_notifications feature flag is enabled", :aggregate_failures do
|
||||
allow(FeatureFlag).to receive(:enabled?).twice.with(:mobile_notifications).and_return(true)
|
||||
|
||||
expect(post: devices_path).to route_to(
|
||||
controller: "devices",
|
||||
action: "create",
|
||||
format: :json,
|
||||
locale: nil,
|
||||
)
|
||||
|
||||
expect(delete: "#{devices_path}/1").to route_to(
|
||||
controller: "devices",
|
||||
action: "destroy",
|
||||
id: "1",
|
||||
format: :json,
|
||||
locale: nil,
|
||||
)
|
||||
end
|
||||
|
||||
it "does not render the user devices routes if the mobile_notifications feature flag is disabled",
|
||||
:aggregate_failures do
|
||||
allow(FeatureFlag).to receive(:enabled?).at_least(:twice).with(:mobile_notifications).and_return(false)
|
||||
|
||||
expect(post: devices_path).not_to route_to(
|
||||
controller: "devices",
|
||||
action: "create",
|
||||
format: :json,
|
||||
locale: nil,
|
||||
)
|
||||
|
||||
expect(delete: "#{devices_path}/1").not_to route_to(
|
||||
controller: "devices",
|
||||
action: "destroy",
|
||||
id: "1",
|
||||
format: :json,
|
||||
locale: nil,
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
@ -97,22 +97,4 @@ RSpec.describe Notifications::NewComment::Send, type: :service do
|
|||
expect(Notification.where(notifiable_type: "Comment", notifiable_id: child_comment.id,
|
||||
organization_id: organization.id)).to be_any
|
||||
end
|
||||
|
||||
it "sends Push Notifications using Pusher Beams when configured" do
|
||||
allow(ApplicationConfig).to receive(:[]).with("PUSHER_BEAMS_KEY").and_return("x" * 64)
|
||||
allow(ApplicationConfig).to receive(:[]).with("APP_PROTOCOL").and_return("http://")
|
||||
allow(ApplicationConfig).to receive(:[]).with("APP_DOMAIN").and_return("localhost:3000")
|
||||
|
||||
allow(Pusher::PushNotifications).to receive(:publish_to_interests)
|
||||
|
||||
comment_sent = child_comment
|
||||
described_class.call(comment_sent)
|
||||
|
||||
channels = ["user-notifications-#{user2.id}", "user-notifications-#{user.id}"]
|
||||
payload = described_class.new(comment_sent).__send__(:push_notification_payload)
|
||||
expect(Pusher::PushNotifications).to have_received(:publish_to_interests) do |block|
|
||||
expect(block[:interests]).to match_array(channels)
|
||||
expect(block[:payload]).to eq(payload)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -20,18 +20,8 @@ RSpec.describe PushNotifications::Send, type: :service do
|
|||
}
|
||||
end
|
||||
|
||||
context "with feature flag set to false/off" do
|
||||
before { allow(FeatureFlag).to receive(:enabled?).with(:mobile_notifications).and_return(false) }
|
||||
|
||||
it "does nothing if the feature flag is disabled" do
|
||||
expect { described_class.call(**params) }
|
||||
.not_to change { Rpush::Client::Redis::Notification.all.count }
|
||||
end
|
||||
end
|
||||
|
||||
context "with no devices for user" do
|
||||
before do
|
||||
allow(FeatureFlag).to receive(:enabled?).with(:mobile_notifications).and_return(true)
|
||||
user.devices.delete
|
||||
end
|
||||
|
||||
|
|
@ -44,7 +34,6 @@ RSpec.describe PushNotifications::Send, type: :service do
|
|||
|
||||
context "with devices for one user" do
|
||||
before do
|
||||
allow(FeatureFlag).to receive(:enabled?).with(:mobile_notifications).and_return(true)
|
||||
allow(ApplicationConfig).to receive(:[]).with("RPUSH_IOS_PEM").and_return("dGVzdGluZw==")
|
||||
allow(ApplicationConfig).to receive(:[]).with("COMMUNITY_NAME").and_return("Forem")
|
||||
create(:device, user: user)
|
||||
|
|
@ -67,7 +56,6 @@ RSpec.describe PushNotifications::Send, type: :service do
|
|||
|
||||
context "with devices for multiple users" do
|
||||
before do
|
||||
allow(FeatureFlag).to receive(:enabled?).with(:mobile_notifications).and_return(true)
|
||||
allow(ApplicationConfig).to receive(:[]).with("RPUSH_IOS_PEM").and_return("dGVzdGluZw==")
|
||||
allow(ApplicationConfig).to receive(:[]).with("COMMUNITY_NAME").and_return("Forem")
|
||||
create(:device, user: user)
|
||||
|
|
|
|||
BIN
vendor/cache/pusher-push-notifications-2.0.1.gem
vendored
BIN
vendor/cache/pusher-push-notifications-2.0.1.gem
vendored
Binary file not shown.
Loading…
Add table
Reference in a new issue