pushing 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.travis.yml +28 -0
  4. data/Appraisals +29 -0
  5. data/CODE_OF_CONDUCT.md +74 -0
  6. data/Gemfile +17 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +225 -0
  9. data/Rakefile +22 -0
  10. data/bin/console +14 -0
  11. data/bin/setup +8 -0
  12. data/certs/apns_example_production.pem.enc +0 -0
  13. data/gemfiles/rails_42.gemfile +17 -0
  14. data/gemfiles/rails_50.gemfile +17 -0
  15. data/gemfiles/rails_51.gemfile +17 -0
  16. data/gemfiles/rails_edge.gemfile +20 -0
  17. data/lib/generators/pushing/USAGE +14 -0
  18. data/lib/generators/pushing/notifier_generator.rb +46 -0
  19. data/lib/generators/pushing/templates/application_notifier.rb +4 -0
  20. data/lib/generators/pushing/templates/initializer.rb +10 -0
  21. data/lib/generators/pushing/templates/notifier.rb +11 -0
  22. data/lib/generators/pushing/templates/template.json+apn.jbuilder +53 -0
  23. data/lib/generators/pushing/templates/template.json+fcm.jbuilder +75 -0
  24. data/lib/pushing.rb +16 -0
  25. data/lib/pushing/adapters.rb +43 -0
  26. data/lib/pushing/adapters/apn/apnotic_adapter.rb +86 -0
  27. data/lib/pushing/adapters/apn/houston_adapter.rb +33 -0
  28. data/lib/pushing/adapters/apn/lowdown_adapter.rb +70 -0
  29. data/lib/pushing/adapters/fcm/andpush_adapter.rb +47 -0
  30. data/lib/pushing/adapters/fcm/fcm_gem_adapter.rb +52 -0
  31. data/lib/pushing/adapters/test_adapter.rb +37 -0
  32. data/lib/pushing/base.rb +187 -0
  33. data/lib/pushing/delivery_job.rb +31 -0
  34. data/lib/pushing/log_subscriber.rb +44 -0
  35. data/lib/pushing/notification_delivery.rb +79 -0
  36. data/lib/pushing/platforms.rb +91 -0
  37. data/lib/pushing/railtie.rb +25 -0
  38. data/lib/pushing/rescuable.rb +28 -0
  39. data/lib/pushing/template_handlers.rb +15 -0
  40. data/lib/pushing/template_handlers/jbuilder_handler.rb +17 -0
  41. data/lib/pushing/version.rb +3 -0
  42. data/pushing.gemspec +30 -0
  43. metadata +211 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 562381d6f82d103cc6e5db2b3408ea36d28fdb7a
4
+ data.tar.gz: 4df883a714c37d2a87f7555b75eb1cce48d5f370
5
+ SHA512:
6
+ metadata.gz: be1bde88cd14ce57d6196cffb630bcf228d89e47040c7cce38e3887b6291bc32ff0edf83ed93ce3532deb1b9f477ab9990f0fae094df5a9f1566ed4816524e42
7
+ data.tar.gz: 060ca860f1f7960dfa264c4d5fd3ee6aa21fa11546cd6dcf6ce6cc87e6d879a16f0ab1211b9715925938fc64e38251cc14771767c5437a59d355f226ad44cce9
data/.gitignore ADDED
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ gemfiles/*.lock
11
+ certs/apns_example_production.pem
12
+ gemfiles/.bundle/
data/.travis.yml ADDED
@@ -0,0 +1,28 @@
1
+ nguage: ruby
2
+ script: bundle exec rake test test:isolated test:integration
3
+ cache: bundler
4
+ sudo: false
5
+
6
+ before_install:
7
+ openssl aes-256-cbc -K $encrypted_86ed69f44076_key -iv $encrypted_86ed69f44076_iv -in ./certs/apns_example_production.pem.enc -out certs/apns_example_production.pem -d
8
+
9
+ rvm:
10
+ - 2.2.7
11
+ - 2.3.4
12
+ - 2.4.1
13
+ - ruby-head
14
+ - jruby-9.1.10.0
15
+ - jruby-head
16
+
17
+ gemfile:
18
+ - gemfiles/rails_42.gemfile
19
+ - gemfiles/rails_50.gemfile
20
+ - gemfiles/rails_51.gemfile
21
+ - gemfiles/rails_edge.gemfile
22
+
23
+ matrix:
24
+ allow_failures:
25
+ - rvm: ruby-head
26
+ - rvm: jruby-9.1.10.0
27
+ - rvm: jruby-head
28
+ - gemfile: gemfiles/rails_edge.gemfile
data/Appraisals ADDED
@@ -0,0 +1,29 @@
1
+ appraise "rails_edge" do
2
+ git 'git://github.com/rails/rails.git' do
3
+ gem "railties"
4
+ gem "actionpack"
5
+ gem "actionview"
6
+ gem "activejob"
7
+ end
8
+ end
9
+
10
+ appraise "rails_50" do
11
+ gem "railties", '~> 5.0.0'
12
+ gem "actionpack", '~> 5.0.0'
13
+ gem "actionview", '~> 5.0.0'
14
+ gem "activejob", '~> 5.0.0'
15
+ end
16
+
17
+ appraise "rails_51" do
18
+ gem "railties", '~> 5.1.0'
19
+ gem "actionpack", '~> 5.1.0'
20
+ gem "actionview", '~> 5.1.0'
21
+ gem "activejob", '~> 5.1.0'
22
+ end
23
+
24
+ appraise "rails_42" do
25
+ gem "rails", '~> 4.2.0'
26
+ gem "actionpack", '~> 4.2.0'
27
+ gem "actionview", '~> 4.2.0'
28
+ gem "activejob", '~> 4.2.0'
29
+ end
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at mail@yukinishijima.net. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,17 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in pushing.gemspec
4
+ gemspec
5
+
6
+ # APNs
7
+ gem 'houston', require: false
8
+ gem 'apnotic', require: false
9
+ gem 'lowdown', require: false
10
+
11
+ # FCM
12
+ gem 'andpush', require: false
13
+ gem 'fcm', require: false
14
+
15
+ # Debugging
16
+ gem 'pry'
17
+ gem 'pry-byebug', platforms: :mri
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Yuki Nishijima
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,225 @@
1
+ # Pushing [![Build Status](https://travis-ci.org/yuki24/pushing.svg?branch=master)](https://travis-ci.org/yuki24/pushing)
2
+
3
+ Pushing is a push notification framework that implements interfaces similar to ActionMailer's APIs.
4
+
5
+ * **Convention over Configuration**: Pushing brings Convention over Configuration to your app's push notification implementation.
6
+ * **Extremely Easy to Learn**: If you know how to use ActionMailer, you already know how to use Pushing. Send notifications asynchronously with ActiveJob at no learning cost.
7
+ * **Testability**: First-class support for push notification. No more hassle writing custom code or stubs/mocks for your tests.
8
+
9
+ ## Getting Started
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'pushing', github: 'yuki24/pushing'
15
+ gem 'jbuilder'
16
+ ```
17
+
18
+ As the time of writing, Pushing only has support for [jbuilder](https://github.com/rails/jbuilder) (Rails' default JSON constructor), but there are plans to add support for [jb](https://github.com/amatsuda/jb) and [rabl](https://github.com/nesquena/rabl).
19
+
20
+ ### Supported Client Gems
21
+
22
+ Pushing itself doesn't make HTTP requests. Instead, it uses an adapter and let an underlaying gem do it. Currently, Pushing has support for the following client gems:
23
+
24
+ * [APNs](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/APNSOverview.html#//apple_ref/doc/uid/TP40008194-CH8-SW1):
25
+ * [anpotic](https://github.com/ostinelli/apnotic) (recommended)
26
+ * [lowdown](https://github.com/alloy/lowdown)
27
+ * [houston](https://github.com/nomad/houston)
28
+
29
+ * [FCM](https://firebase.google.com/docs/cloud-messaging/):
30
+ * [andpush](https://github.com/yuki24/andpush) (recommended)
31
+ * [fcm](https://github.com/spacialdb/fcm)
32
+
33
+ If you are starting from scratch, it is recommended using [anpotic](https://github.com/ostinelli/apnotic) for APNs and [andpush](https://github.com/yuki24/andpush) for FCM due to their reliability and performance:
34
+
35
+ ```ruby
36
+ gem 'apnotic' # APNs integration
37
+ gem 'andpush' # FCM integration
38
+ ```
39
+
40
+ ### Walkthrough to Writing a Notifier
41
+
42
+ In this README, we'll use Twitter as an example. Suppose you'd like to send a push notification when a user receives a new direct message from other user. To get started, you can use Pushing's notifier generator:
43
+
44
+ ```sh
45
+ $ rails g pushing:notifier TweetNotifier new_direct_message
46
+ ```
47
+
48
+ #### Edit the Notifier
49
+
50
+ Let's say there are `direct_messages` and `device_tokens` tables where we store actual messages and device tokens (a.k.a registration ids in FCM) given by APNs or FCM.
51
+
52
+ ```ruby
53
+ # app/notifiers/tweet_notifier.rb
54
+ class TweetNotifier < ApplicationNotifier
55
+ def new_direct_message(message_id, token_id)
56
+ @message = DirectMessage.find(message_id)
57
+ @token = DeviceToken.find(token_id)
58
+
59
+ push apn: @token.apn? && @token.device_token, fcm: @token.fcm?
60
+ end
61
+ end
62
+ ```
63
+
64
+ Notice that the `:apn` key takes a truthy string value while the `:fcm` key takes a boolean value. Also, Pushing only sends a notification for the platforms that are given a truthy value. For example, the call:
65
+
66
+ ```ruby
67
+ # only sends a push notification to FCM
68
+ push apn: false, fcm: @token.registration_id
69
+ ```
70
+
71
+ will only send a notification to the FCM service.
72
+
73
+ #### Edit the Push Notification Payload
74
+
75
+ Next, let's modify the templates to generate JSON that contains message data. Like controllers, you can use all the instance variables initialized in the action.
76
+
77
+ APNs:
78
+
79
+ ```ruby
80
+ # app/views/tweet_notifier/new_direct_message.json+apn.jbuilder
81
+ json.aps do
82
+ json.alert do
83
+ json.title "#{@tweet.user.display_name} tweeted:"
84
+ json.body truncate(@tweet.body, length: 235)
85
+ end
86
+
87
+ json.badge 1
88
+ json.sound 'bingbong.aiff'
89
+ end
90
+ ```
91
+
92
+ FCM:
93
+
94
+ ```ruby
95
+ # app/views/tweet_notifier/new_direct_message.json+fcm.jbuilder
96
+ json.to @token.registration_id
97
+
98
+ json.notification do
99
+ json.title "#{@tweet.user.display_name} tweeted:"
100
+ json.body truncate(@tweet.body, length: 1024)
101
+
102
+ json.icon 1
103
+ json.sound 'default'
104
+ end
105
+ ```
106
+
107
+ ### Deliver the Push Notifications
108
+
109
+ Finally, send a push notification to the user. You can call the `#deliver_now!` method to immediately send a notification, or the `#deliver_later!` method if you have ActiveJob set up.
110
+
111
+ ```ruby
112
+ TweetNotifier.new_direct_message(message_id, device_token.id).deliver_now!
113
+ # => sends a push notification immediately
114
+
115
+ TweetNotifier.new_direct_message(message_id, device_token.id).deliver_later!
116
+ # => enqueues a job that sends a push notification later
117
+ ```
118
+
119
+ ## Error Handling
120
+
121
+ Like ActionMailer, you can use the `rescue_from` hook to handle exceptions. A common use-case would be to handle a **'BadDeviceToken'** response from APNs or a response with a **'Retry-After'** header from FCM.
122
+
123
+ **Handling a 'BadDeviceToken' response from APNs**:
124
+
125
+ ```ruby
126
+ class ApplicationNotifier < Pushing::Base
127
+ rescue_from Pushing::ApnDeliveryError do |error|
128
+ response = error.response
129
+
130
+ if response.status == 410 || (response.status == 400 && response.json[:reason] == 'BadDeviceToken')
131
+ token = error.notification.device_token
132
+
133
+ # delete device token accordingly
134
+ else
135
+ raise # Make sure to raise any other types of error to re-enqueue the job
136
+ end
137
+ end
138
+ end
139
+ ```
140
+
141
+ **Handling a 'Retry-After' header from FCM**:
142
+
143
+ ```ruby
144
+ class ApplicationNotifier < Pushing::Base
145
+ rescue_from Pushing::FcmDeliveryError do |error|
146
+ if error.response&.headers['Retry-After']
147
+ # re-enqueue the job honoring the 'Retry-After' header
148
+ else
149
+ raise # Make sure to raise any other types of error to re-enqueue the job
150
+ end
151
+ end
152
+ end
153
+ ```
154
+
155
+ ## Interceptors and Observers
156
+
157
+ Pushing implements the Interceptor and Observer patterns. A common use-case would be to update registration ids with canonical ids from FCM:
158
+
159
+ ```ruby
160
+ # app/observers/fcm_token_handler.rb
161
+ class FcmTokenHandler
162
+ def delivered_notification(payload, response)
163
+ return if response.json[:canonical_ids].to_i.zero?
164
+
165
+ response.json[:results].select {|result| result[:registration_id] }.each do |result|
166
+ result[:registration_id] # => returns a canonical id
167
+
168
+ # Update registration ids accordingly
169
+ end
170
+ end
171
+ end
172
+
173
+ # app/notifiers/application_notifier.rb
174
+ class ApplicationNotifier < Pushing::Base
175
+ register_observer FcmTokenHandler.new
176
+
177
+ ...
178
+ end
179
+ ```
180
+
181
+ ## Configuration
182
+
183
+ TODO
184
+
185
+ ## Testing
186
+
187
+ Pushing provides first-class support for testing. In the test environment, use the `:test` adapter instead of an actual adapter you'd like to use in development/production.
188
+
189
+ ```ruby
190
+ # config/initializers/pushing.rb
191
+ Pushing::Platforms.configure do |config|
192
+ config.apn.adapter = Rails.env.test? ? :test : :apnotic
193
+ config.fcm.adapter = Rails.env.test? ? :test : :andpush
194
+ end
195
+ ```
196
+
197
+ Now you can use the `#deliveries` method. Here is an example with [ActiveSupport::TestCase](http://api.rubyonrails.org/classes/ActiveSupport/TestCase.html):
198
+
199
+ ```ruby
200
+ TweetNotifier.deliveries.clear # => clears the test inbox
201
+
202
+ assert_changes -> { TweetNotifier.deliveries.apn.size }, from: 0, to: 1 do
203
+ TweetNotifier.new_direct_message(message.id, apn_device_token.id).deliver_now!
204
+ end
205
+
206
+ apn_message = TweetNotifier.deliveries.apn.first
207
+ assert_equal 'apn-device-token', apn_message.device_token
208
+ assert_equal "Hey coffee break?", apn_message.payload[:aps][:alert][:body]
209
+
210
+ assert_changes -> { TweetNotifier.deliveries.fcm.size }, from: 0, to: 1 do
211
+ TweetNotifier.new_direct_message(message.id, fcm_registration_id.id).deliver_now!
212
+ end
213
+
214
+ fcm_payload = TweetNotifier.deliveries.fcm.first.payload
215
+ assert_equal 'fcm-registration-id', fcm_payload[:to]
216
+ assert_equal "Hey coffee break?", fcm_payload[:notification][:body]
217
+ ```
218
+
219
+ ## Contributing
220
+
221
+ Bug reports and pull requests are welcome on GitHub at https://github.com/yuki24/pushing. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
222
+
223
+ ## License
224
+
225
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,22 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/**/*_test.rb'] - FileList['test/integration/**/*'] - ["test/railtie_test.rb"]
8
+ end
9
+
10
+ Rake::TestTask.new('test:integration') do |t|
11
+ t.libs << "test"
12
+ t.libs << "lib"
13
+ t.test_files = FileList['test/integration/**/*_test.rb']
14
+ end
15
+
16
+ Rake::TestTask.new('test:isolated') do |t|
17
+ t.libs << "test"
18
+ t.libs << "lib"
19
+ t.test_files = ["test/railtie_test.rb"]
20
+ end
21
+
22
+ task default: [:test, 'test:isolated']