Skip to content

Commit a1738d4

Browse files
Merge pull request #2 from jesseplusplus/features/expo-push
Allow push notifications via Expo
2 parents 9063029 + db9cf81 commit a1738d4

File tree

7 files changed

+86
-12
lines changed

7 files changed

+86
-12
lines changed

app/controllers/api/v1/push/subscriptions_controller.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ def create
1313
endpoint: subscription_params[:endpoint],
1414
key_p256dh: subscription_params[:keys][:p256dh],
1515
key_auth: subscription_params[:keys][:auth],
16+
expo: subscription_params[:expo],
1617
data: data_params,
1718
user_id: current_user.id,
1819
access_token_id: doorkeeper_token.id
@@ -46,7 +47,7 @@ def check_push_subscription
4647
end
4748

4849
def subscription_params
49-
params.require(:subscription).permit(:endpoint, keys: [:auth, :p256dh])
50+
params.require(:subscription).permit(:endpoint, :expo, keys: [:auth, :p256dh])
5051
end
5152

5253
def data_params

app/models/web/push_subscription.rb

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@
55
#
66
# id :bigint(8) not null, primary key
77
# endpoint :string not null
8-
# key_p256dh :string not null
9-
# key_auth :string not null
8+
# key_p256dh :string
9+
# key_auth :string
1010
# data :json
1111
# created_at :datetime not null
1212
# updated_at :datetime not null
1313
# access_token_id :bigint(8)
1414
# user_id :bigint(8)
15+
# expo :string
1516
#
1617

1718
class Web::PushSubscription < ApplicationRecord
@@ -21,8 +22,8 @@ class Web::PushSubscription < ApplicationRecord
2122
has_one :session_activation, foreign_key: 'web_push_subscription_id', inverse_of: :web_push_subscription
2223

2324
validates :endpoint, presence: true
24-
validates :key_p256dh, presence: true
25-
validates :key_auth, presence: true
25+
validates :key_p256dh, presence: true, unless: :expo?
26+
validates :key_auth, presence: true, unless: :expo?
2627

2728
delegate :locale, to: :associated_user
2829

app/workers/web/push_notification_worker.rb

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@ def perform(subscription_id, notification_id)
1616
# in the meantime, so we have to double-check before proceeding
1717
return unless @notification.activity.present? && @subscription.pushable?(@notification)
1818

19-
payload = @subscription.encrypt(push_notification_json)
19+
return expo_send if @subscription.expo?
2020

21+
payload = @subscription.encrypt(push_notification_json)
22+
2123
request_pool.with(@subscription.audience) do |http_client|
2224
request = Request.new(:post, @subscription.endpoint, body: payload.fetch(:ciphertext), http_client: http_client)
2325

@@ -38,7 +40,7 @@ def perform(subscription_id, notification_id)
3840
# and must be removed
3941

4042
if (400..499).cover?(response.code) && ![408, 429].include?(response.code)
41-
@subscription.destroy!
43+
raise Mastodon::UnexpectedResponseError, response
4244
elsif !(200...300).cover?(response.code)
4345
raise Mastodon::UnexpectedResponseError, response
4446
end
@@ -50,6 +52,29 @@ def perform(subscription_id, notification_id)
5052

5153
private
5254

55+
def expo_send
56+
request_pool.with(@subscription.audience) do |http_client|
57+
body = push_notification_json
58+
59+
request = Request.new(:post, @subscription.endpoint, body: body, http_client: http_client)
60+
61+
request.add_headers(
62+
'Content-Type' => 'application/json',
63+
'Accept' => 'application/json',
64+
'Accept-Encoding' => 'gzip, deflate',
65+
'Host' => 'exp.host',
66+
'Ttl' => TTL,
67+
'Urgency' => URGENCY,
68+
)
69+
70+
request.perform do |response|
71+
if !(200...300).cover?(response.code)
72+
raise Mastodon::UnexpectedResponseError, response
73+
end
74+
end
75+
end
76+
end
77+
5378
def push_notification_json
5479
json = I18n.with_locale(@subscription.locale || I18n.default_locale) do
5580
ActiveModelSerializers::SerializableResource.new(
@@ -60,6 +85,15 @@ def push_notification_json
6085
).as_json
6186
end
6287

88+
if (@subscription.expo?)
89+
json.delete :access_token
90+
json.delete :preferred_locale
91+
json.delete :notification_id
92+
json.delete :notification_type
93+
94+
json[:to] = @subscription.expo
95+
end
96+
6397
Oj.dump(json)
6498
end
6599

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
class AddExpoToWebPushSubscriptions < ActiveRecord::Migration[6.1]
2+
def up
3+
safety_assured do
4+
change_table(:web_push_subscriptions) do |t|
5+
t.column :expo, :string
6+
t.change :key_p256dh, :string, null: true
7+
t.change :key_auth, :string, null: true
8+
end
9+
end
10+
end
11+
def down
12+
safety_assured do
13+
change_table(:web_push_subscriptions) do |t|
14+
t.remove :expo
15+
t.change :key_p256dh, :string, null: false
16+
t.change :key_auth, :string, null: false
17+
end
18+
end
19+
end
20+
end

db/schema.rb

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
#
1111
# It's strongly recommended that you check this file into your version control system.
1212

13-
ActiveRecord::Schema.define(version: 2021_06_09_202149) do
13+
ActiveRecord::Schema.define(version: 2021_07_08_211904) do
1414

1515
# These are extensions that must be enabled in order to support this database
1616
enable_extension "plpgsql"
@@ -937,13 +937,14 @@
937937

938938
create_table "web_push_subscriptions", force: :cascade do |t|
939939
t.string "endpoint", null: false
940-
t.string "key_p256dh", null: false
941-
t.string "key_auth", null: false
940+
t.string "key_p256dh"
941+
t.string "key_auth"
942942
t.json "data"
943943
t.datetime "created_at", null: false
944944
t.datetime "updated_at", null: false
945945
t.bigint "access_token_id"
946946
t.bigint "user_id"
947+
t.string "expo"
947948
t.index ["access_token_id"], name: "index_web_push_subscriptions_on_access_token_id"
948949
t.index ["user_id"], name: "index_web_push_subscriptions_on_user_id"
949950
end

spec/controllers/api/v1/push/subscriptions_controller_spec.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
p256dh: 'BEm_a0bdPDhf0SOsrnB2-ategf1hHoCnpXgQsFj5JCkcoMrMt2WHoPfEYOYPzOIs9mZE8ZUaD7VA5vouy0kEkr8=',
2121
auth: 'eH_C8rq2raXqlcBVDa1gLg==',
2222
},
23+
expo: 'token1234'
2324
}
2425
}.with_indifferent_access
2526
end
@@ -53,6 +54,7 @@
5354
expect(push_subscription.endpoint).to eq(create_payload[:subscription][:endpoint])
5455
expect(push_subscription.key_p256dh).to eq(create_payload[:subscription][:keys][:p256dh])
5556
expect(push_subscription.key_auth).to eq(create_payload[:subscription][:keys][:auth])
57+
expect(push_subscription.expo).to eq(create_payload[:subscription][:expo])
5658
expect(push_subscription.user_id).to eq user.id
5759
expect(push_subscription.access_token_id).to eq token.id
5860
end

spec/workers/web/push_notification_worker_spec.rb

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,11 @@
2929
allow(JWT).to receive(:encode).and_return('jwt.encoded.payload')
3030

3131
stub_request(:post, endpoint).to_return(status: 201, body: '')
32-
33-
subject.perform(subscription.id, notification.id)
3432
end
3533

3634
it 'calls the relevant service with the correct headers' do
35+
subject.perform(subscription.id, notification.id)
36+
3737
expect(a_request(:post, endpoint).with(headers: {
3838
'Content-Encoding' => 'aesgcm',
3939
'Content-Type' => 'application/octet-stream',
@@ -44,5 +44,20 @@
4444
'Authorization' => 'WebPush jwt.encoded.payload',
4545
}, body: "+\xB8\xDBT}\u0013\xB6\xDD.\xF9\xB0\xA7\xC8Ҁ\xFD\x99#\xF7\xAC\x83\xA4\xDB,\u001F\xB5\xB9w\x85>\xF7\xADr")).to have_been_made
4646
end
47+
48+
it 'calls the relevant service with the correct headers and body when it is an expo subscription' do
49+
allow_any_instance_of(subscription.class).to receive(:expo?).and_return(true)
50+
allow_any_instance_of(subscription.class).to receive(:expo).and_return('ExpoToken1234')
51+
subject.perform(subscription.id, notification.id)
52+
53+
expect(a_request(:post, endpoint).with(headers: {
54+
'Content-Type' => 'application/json',
55+
'Accept' => 'application/json',
56+
'Accept-Encoding' => 'gzip, deflate',
57+
'Host' => 'exp.host',
58+
'Ttl' => '172800',
59+
'Urgency' => 'normal',
60+
}, body: { to: 'ExpoToken1234', title: be_an_instance_of(String), body: be_an_instance_of(String), icon: be_an_instance_of(String)})).to have_been_made
61+
end
4762
end
4863
end

0 commit comments

Comments
 (0)