pezza_action_push_web 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +299 -0
  4. data/Rakefile +8 -0
  5. data/app/assets/javascripts/action_push_web.js +424 -0
  6. data/app/assets/javascripts/components/action_push_web.js +7 -0
  7. data/app/assets/javascripts/components/denied.js +24 -0
  8. data/app/assets/javascripts/components/granted.js +86 -0
  9. data/app/assets/javascripts/components/request.js +55 -0
  10. data/app/assets/stylesheets/action_push_web/application.css +15 -0
  11. data/app/controllers/action_push_web/subscriptions_controller.rb +19 -0
  12. data/app/helpers/action_push_web/application_helper.rb +22 -0
  13. data/app/jobs/action_push_web/notification_job.rb +48 -0
  14. data/app/models/action_push_web/subscription.rb +13 -0
  15. data/db/migrate/20250907213606_create_action_push_web_subscriptions.rb +13 -0
  16. data/lib/action_push_web/engine.rb +36 -0
  17. data/lib/action_push_web/errors.rb +8 -0
  18. data/lib/action_push_web/notification.rb +63 -0
  19. data/lib/action_push_web/payload_encryption.rb +86 -0
  20. data/lib/action_push_web/pool.rb +46 -0
  21. data/lib/action_push_web/pusher.rb +85 -0
  22. data/lib/action_push_web/subscription_notification.rb +13 -0
  23. data/lib/action_push_web/vapid_key.rb +38 -0
  24. data/lib/action_push_web/vapid_key_generator.rb +19 -0
  25. data/lib/action_push_web/version.rb +3 -0
  26. data/lib/action_push_web.rb +37 -0
  27. data/lib/generators/action_push_web/install/install_generator.rb +51 -0
  28. data/lib/generators/action_push_web/install/templates/app/jobs/application_push_web_notification_job.rb.tt +7 -0
  29. data/lib/generators/action_push_web/install/templates/app/models/application_push_subscription.rb.tt +4 -0
  30. data/lib/generators/action_push_web/install/templates/app/models/application_push_web_notification.rb.tt +12 -0
  31. data/lib/generators/action_push_web/install/templates/app/views/pwa/service-worker.js +30 -0
  32. data/lib/generators/action_push_web/install/templates/config/push.yml.tt +22 -0
  33. data/lib/pezza_action_push_web.rb +4 -0
  34. data/lib/tasks/action_push_web_tasks.rake +4 -0
  35. metadata +160 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 6b803e5c0eef3a7959489bf3949295a1b0e18ea5d2a9948f4e5a72a50278ce7c
4
+ data.tar.gz: ceb797d01a9e63505b31a7e1caf0eea9b3fbd79082562c392a2a46405755e225
5
+ SHA512:
6
+ metadata.gz: e1a7b5f5b15cd74247f32cd3d5101bdae3ef36e49acfadafce656fdafacce379dc907faf7f6bf7031965a555e338cedfd8705ca42c93bb2e9768868d9b6b8b6c
7
+ data.tar.gz: cafbcc9aecefd513b408d246626d2e6f553bb01968b38345dd8030e1105376d75fa0460faac25e886469b66a9174176ade363c81809675513b63543118cfaf3a
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright Nick Pezza
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,299 @@
1
+ # ActionPushWeb
2
+
3
+ Action Push Web is a Rails push notification gem for the web and PWAs.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ 1. bundle add action_push_web
9
+ 2. bin/rails g action_push_web:install
10
+ 4. bin/rails db:migrate
11
+ ```
12
+
13
+ This will install the gem and run the necessary migrations to set up the database.
14
+
15
+ The install generator will also output a generated public and private key that
16
+ you'll want to add to your credentitals.
17
+
18
+ ## Configuration
19
+
20
+ The installation will create:
21
+
22
+ - `app/models/application_push_web_notification.rb`
23
+ - `app/jobs/application_push_web_notification_job.rb`
24
+ - `app/models/application_push_subscription.rb`
25
+ - `config/push.yml`
26
+ - `app/views/pwa/service_worker.js`
27
+ - mount the subscriptions controllers
28
+ - import action_push_web.js in your application.js
29
+ - Add `<%= action_push_web_key_tag %>` to the <head> of your application's layout if it can find it. Otherwise, you'll need to add it manually.
30
+
31
+
32
+ `app/models/application_push_web_notification.rb`:
33
+ ```ruby
34
+ class ApplicationPushWebNotification < ActionPushWeb::Notification
35
+ # Set a custom job queue_name
36
+ queue_as :realtime
37
+
38
+ # Controls whether push notifications are enabled (default: !Rails.env.test?)
39
+ self.enabled = Rails.env.production?
40
+
41
+ # Define a custom callback to modify or abort the notification before it is sent
42
+ before_delivery do |notification|
43
+ throw :abort if Notification.find(notification.context[:notification_id]).expired?
44
+ end
45
+ end
46
+ ```
47
+
48
+ Used to create and send push notifications. You can customize it by subclassing or
49
+ you can change the application defaults by editing it directly.
50
+
51
+ `app/jobs/application_push_web_notification_job.rb`:
52
+
53
+ ```ruby
54
+ class ApplicationPushWebNotificationJob < ActionPushWeb::NotificationJob
55
+ # Enable logging job arguments (default: false)
56
+ self.log_arguments = true
57
+
58
+ # Report job retries via the `Rails.error` reporter (default: false)
59
+ self.report_job_retries = true
60
+ end
61
+ ```
62
+
63
+ Job class that processes the push notifications. You can customize it by editing it
64
+ directly in your application.
65
+
66
+ `app/models/application_push_subscription.rb`:
67
+
68
+ ```ruby
69
+ class ApplicationPushSubscription < ActionPushWeb::Subscription
70
+ # Customize TokenError handling (default: destroy!)
71
+ # rescue_from (ActionPushWeb::TokenError) { Rails.logger.error("Subscription #{id} token is invalid") }
72
+ end
73
+ ```
74
+
75
+ This represents a push notification subscription. You can customize it by editing it directly in your application.
76
+
77
+ `config/push.yml`:
78
+
79
+ ```yaml
80
+ shared:
81
+ web:
82
+ public_key: <%= Rails.application.credentials.action_push_web.public_key %>
83
+ private_key: <%= Rails.application.credentials.action_push_web.private_key %>
84
+
85
+ # Change the request timeout (default: 30).
86
+ # request_timeout: 60
87
+
88
+ # Change the ttl (default: 2419200).
89
+ # ttl: 60
90
+
91
+ # Change the expiration (default: 43200).
92
+ # expiration: 60
93
+
94
+ # Change the subject (default: mailto:sender@example.com).
95
+ # expiration: mailto:support@my-domain.com
96
+
97
+ # Change the urgency (default: normal). You also choose to set this at the notification level.
98
+ # urgency: high
99
+ ```
100
+
101
+ This file contains the configuration for the push notification services you want to use.
102
+ The push notification requires a web key with a public and private key.
103
+ If you're configuring more than one app, see the section [Configuring multiple apps](#configuring-multiple-apps) below.
104
+
105
+ ### Configuring multiple apps
106
+
107
+ You can send push notifications to multiple apps using different notification classes.
108
+ Each notification class need to inherit from `ApplicationPushWebNotification` and set `self.application`, to a key set in `push.yml`
109
+ for each supported platform. You can also (optionally) set a shared `application` option in `push.yml`.
110
+ This acts as the base configuration for that platform, and its values will be merged (and overridden) with the matching app-specific configuration.
111
+
112
+ In the example below we are configuring two apps: `calendar` and `email` using respectively the
113
+ `CalendarPushNotification` and `EmailPushNotification` notification classes.
114
+
115
+ ```ruby
116
+ class CalendarPushNotification < ApplicationPushWebNotification
117
+ self.application = "calendar"
118
+
119
+ # Custom notification logic for calendar app
120
+ end
121
+
122
+ class EmailPushNotification < ApplicationPushWebNotification
123
+ self.application = "email"
124
+
125
+ # Custom notification logic for email app
126
+ end
127
+ ```
128
+
129
+ ```yaml
130
+ shared:
131
+ web:
132
+ # Base configuration for web platform
133
+ # This will be merged with the app-specific configuration
134
+ application:
135
+ request_timeout: 60
136
+
137
+ calendar:
138
+ public_key: <%%= Rails.application.credentials.action_push_web.calendar.public_key %>
139
+ private_key: <%%= Rails.application.credentials.action_push_web.calendar.private_key %>
140
+
141
+ email:
142
+ public_key: <%%= Rails.application.credentials.action_push_web.email.public_key %>
143
+ private_key: <%%= Rails.application.credentials.action_push_web.email.private_key %>
144
+ ```
145
+
146
+ ## Usage
147
+
148
+ ### Create and send a notification asynchronously to a subscription
149
+
150
+ ```ruby
151
+ subscription = ApplicationPushSubscription.create! \
152
+ user_agent: "Mozilla/5.0 (iPhone; CPU iPhone OS 18_6_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.6 Mobile/15E148 Safari/604.1",
153
+ auth_key: "foStsVKvFCvKS1KJF4OaDS",
154
+ p256dh_key: "8YZosOgeQYI1lXr6Enahllf56j0VvEynIIm0q37k19QdbclLPNbACud8XSgS1b04TNAFlwyS1niwMx9LoLp8Hsx",
155
+ endpoint: "https://web.push.apple.com/2UtCfdxa01DJYCW0R7qnA9u4JqYnYo5CHSlR0b95JnMhAW1Zy32ZN9BTLY8KLXogMU3EMuYDWNgdUcX8OaNEZCQOhFp7zeo8US2ZvKYdvGxAjx1ELZH9e3yXHEYlco6vKLfsgOCZxabp63rt80voC5n9i6IzAvMgWmcwz5INfBd"
156
+
157
+ notification = ApplicationPushWebNotification.new \
158
+ title: "Hello world!",
159
+ body: "Welcome to Action Push Web",
160
+ path: "/welcome"
161
+
162
+ notification.deliver_later_to(subscription)
163
+ ```
164
+
165
+ `deliver_later_to` supports also an array of devices:
166
+
167
+ ```ruby
168
+ notification.deliver_later_to([ subscription1, subscription2 ])
169
+ ```
170
+
171
+ A notification can also be delivered synchronously using `deliver_to`:
172
+
173
+ ```ruby
174
+ notification.deliver_to(subscription)
175
+ ```
176
+
177
+ It is recommended to send notifications asynchronously using `deliver_later_to`.
178
+ This ensures error handling and retry logic are in place, and avoids blocking your application's execution.
179
+
180
+ ### Linking a Subscription to a Record
181
+
182
+ A Subscription can be associated with any record in your application via the `owner` polymorphic association:
183
+
184
+ ```ruby
185
+ user = User.find_by_email_address("pezza@hey.com")
186
+
187
+ ApplicationPushSubscription.create! \
188
+ user_agent: "Mozilla/5.0 (iPhone; CPU iPhone OS 18_6_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.6 Mobile/15E148 Safari/604.1",
189
+ auth_key: "foStsVKvFCvKS1KJF4OaDS",
190
+ p256dh_key: "8YZosOgeQYI1lXr6Enahllf56j0VvEynIIm0q37k19QdbclLPNbACud8XSgS1b04TNAFlwyS1niwMx9LoLp8Hsx",
191
+ endpoint: "https://web.push.apple.com/2UtCfdxa01DJYCW0R7qnA9u4JqYnYo5CHSlR0b95JnMhAW1Zy32ZN9BTLY8KLXogMU3EMuYDWNgdUcX8OaNEZCQOhFp7zeo8US2ZvKYdvGxAjx1ELZH9e3yXHEYlco6vKLfsgOCZxabp63rt80voC5n9i6IzAvMgWmcwz5INfBd",
192
+ owner: user
193
+ ```
194
+
195
+ ### `before_delivery` callback
196
+
197
+ You can specify Active Record like callbacks for the `delivery` method. For example, you can modify
198
+ or cancel the notification by specifying a custom `before_delivery` block. The callback has access
199
+ to the `notification` object. You can also pass additional context data to the notification
200
+ by adding extra arguments to the notification constructor:
201
+
202
+ ```ruby
203
+ class CalendarPushNotification < ApplicationPushWebNotification
204
+ before_delivery do |notification|
205
+ throw :abort if Calendar.find(notification.context[:calendar_id]).expired?
206
+ end
207
+ end
208
+
209
+ notification = CalendarPushNotification
210
+ .new(title: "Upcoming event", path: "/events/1", calendar_id: 123)
211
+
212
+ notification.deliver_later_to(subscription)
213
+ ```
214
+
215
+ ### Using a custom Subscription model
216
+
217
+ If using the default `ApplicationPushSubscription` model does not fit your needs, you can create a custom
218
+ subscription model, as long as:
219
+
220
+ 1. It can be serialized and deserialized by `ActiveJob`.
221
+ 2. It responds to the `endpoint`, `auth_key` and `p256dh_key` methods.
222
+ 3. It implements a `push` method like this:
223
+
224
+ ```ruby
225
+ class CustomSubscription
226
+ # Your custom device attributes and methods...
227
+
228
+ def push(notification)
229
+ ActionPushWeb.push \
230
+ ActionPushWeb::SubscriptionNotification.new(notification:, subscription: self)
231
+ end
232
+ end
233
+ ```
234
+
235
+ On the frontend, there are 3 custom HTML elements that can be accessed via helpers.
236
+
237
+ The first is when the user has not yet granted permission to send notifications.
238
+
239
+ You can use what ever HTML you want inside these components. Once the user either grants or denies
240
+ permission the component will hide itself.
241
+
242
+ ```erb
243
+ <%= ask_for_web_notifications do %>
244
+ <div class="text-blue">Request permission</div>
245
+ <% end %>
246
+ ```
247
+
248
+ If a user denies permission to send notifications:
249
+
250
+ ```erb
251
+ <%= when_web_notifications_disabled do %>
252
+ <div class="text-red">Notifications aren’t allowed</div>
253
+ <% end %>
254
+ ```
255
+
256
+ And if a user grants permission to send notifications.
257
+ It accepts an `href` attribute to be passed to the helper that points to a create
258
+ action that handles creating a push subscription. By default it points to the
259
+ controller included in ActionPushWeb, `action_push_web.subscriptions_path`. It
260
+ also accepts a `service_worker_url` attribute that points to the service worker.
261
+ By default it points to `pwa_service_worker_path(format: :js)`
262
+
263
+ ```erb
264
+ <%= when_web_notifications_allowed href: action_push_web.subscriptions_path, class: "text-green" do %>
265
+ Notifications are allowed
266
+ <% end %>
267
+ ```
268
+
269
+ You can alternatively create a custom controller that handles creating a push subscription:
270
+
271
+ ```ruby
272
+ class PushSubscriptionsController < ActionPushWeb::SubscriptionsController
273
+ private
274
+ def push_subscription_params
275
+ super.merge(owner: Current.user)
276
+ end
277
+ end
278
+ ```
279
+
280
+ ```erb
281
+ <%= ask_for_web_notifications(href: push_subscriptions_path) do %>
282
+ <div class="text-blue">Request permission</div>
283
+ <% end %>
284
+ ```
285
+
286
+ ## `ActionPushWeb::Notification` attributes
287
+
288
+ | Name | Description
289
+ |------------------|------------
290
+ | :title | The title of the notification.
291
+ | :body | The body of the notification.
292
+ | :badge | The badge number to display on the app icon.
293
+ | :path | The path to open when the user taps on the notification.
294
+ | :icon_path | The path to the icon to display in the notification.
295
+ | :urgency | The urgency of the notification. (very-low \| low \| normal \| high)
296
+ | ** | Any additional attributes passed to the constructor will be merged in the `context` hash.
297
+
298
+ ## License
299
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require "bundler/setup"
2
+
3
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
4
+ load "rails/tasks/engine.rake"
5
+
6
+ load "rails/tasks/statistics.rake"
7
+
8
+ require "bundler/gem_tasks"