pushing 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.travis.yml +28 -0
- data/Appraisals +29 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +17 -0
- data/LICENSE.txt +21 -0
- data/README.md +225 -0
- data/Rakefile +22 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/certs/apns_example_production.pem.enc +0 -0
- data/gemfiles/rails_42.gemfile +17 -0
- data/gemfiles/rails_50.gemfile +17 -0
- data/gemfiles/rails_51.gemfile +17 -0
- data/gemfiles/rails_edge.gemfile +20 -0
- data/lib/generators/pushing/USAGE +14 -0
- data/lib/generators/pushing/notifier_generator.rb +46 -0
- data/lib/generators/pushing/templates/application_notifier.rb +4 -0
- data/lib/generators/pushing/templates/initializer.rb +10 -0
- data/lib/generators/pushing/templates/notifier.rb +11 -0
- data/lib/generators/pushing/templates/template.json+apn.jbuilder +53 -0
- data/lib/generators/pushing/templates/template.json+fcm.jbuilder +75 -0
- data/lib/pushing.rb +16 -0
- data/lib/pushing/adapters.rb +43 -0
- data/lib/pushing/adapters/apn/apnotic_adapter.rb +86 -0
- data/lib/pushing/adapters/apn/houston_adapter.rb +33 -0
- data/lib/pushing/adapters/apn/lowdown_adapter.rb +70 -0
- data/lib/pushing/adapters/fcm/andpush_adapter.rb +47 -0
- data/lib/pushing/adapters/fcm/fcm_gem_adapter.rb +52 -0
- data/lib/pushing/adapters/test_adapter.rb +37 -0
- data/lib/pushing/base.rb +187 -0
- data/lib/pushing/delivery_job.rb +31 -0
- data/lib/pushing/log_subscriber.rb +44 -0
- data/lib/pushing/notification_delivery.rb +79 -0
- data/lib/pushing/platforms.rb +91 -0
- data/lib/pushing/railtie.rb +25 -0
- data/lib/pushing/rescuable.rb +28 -0
- data/lib/pushing/template_handlers.rb +15 -0
- data/lib/pushing/template_handlers/jbuilder_handler.rb +17 -0
- data/lib/pushing/version.rb +3 -0
- data/pushing.gemspec +30 -0
- metadata +211 -0
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "pushing"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
Binary file
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# This file was generated by Appraisal
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
gem "houston", require: false
|
6
|
+
gem "apnotic", require: false
|
7
|
+
gem "lowdown", require: false
|
8
|
+
gem "andpush", require: false
|
9
|
+
gem "fcm", require: false
|
10
|
+
gem "pry"
|
11
|
+
gem "pry-byebug", platforms: :mri
|
12
|
+
gem "rails", "~> 4.2.0"
|
13
|
+
gem "actionpack", "~> 4.2.0"
|
14
|
+
gem "actionview", "~> 4.2.0"
|
15
|
+
gem "activejob", "~> 4.2.0"
|
16
|
+
|
17
|
+
gemspec path: "../"
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# This file was generated by Appraisal
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
gem "houston", require: false
|
6
|
+
gem "apnotic", require: false
|
7
|
+
gem "lowdown", require: false
|
8
|
+
gem "andpush", require: false
|
9
|
+
gem "fcm", require: false
|
10
|
+
gem "pry"
|
11
|
+
gem "pry-byebug", platforms: :mri
|
12
|
+
gem "railties", "~> 5.0.0"
|
13
|
+
gem "actionpack", "~> 5.0.0"
|
14
|
+
gem "actionview", "~> 5.0.0"
|
15
|
+
gem "activejob", "~> 5.0.0"
|
16
|
+
|
17
|
+
gemspec path: "../"
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# This file was generated by Appraisal
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
gem "houston", require: false
|
6
|
+
gem "apnotic", require: false
|
7
|
+
gem "lowdown", require: false
|
8
|
+
gem "andpush", require: false
|
9
|
+
gem "fcm", require: false
|
10
|
+
gem "pry"
|
11
|
+
gem "pry-byebug", platforms: :mri
|
12
|
+
gem "railties", "~> 5.1.0"
|
13
|
+
gem "actionpack", "~> 5.1.0"
|
14
|
+
gem "actionview", "~> 5.1.0"
|
15
|
+
gem "activejob", "~> 5.1.0"
|
16
|
+
|
17
|
+
gemspec path: "../"
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# This file was generated by Appraisal
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
git "git://github.com/rails/rails.git" do
|
6
|
+
gem "railties"
|
7
|
+
gem "actionpack"
|
8
|
+
gem "actionview"
|
9
|
+
gem "activejob"
|
10
|
+
end
|
11
|
+
|
12
|
+
gem "houston", require: false
|
13
|
+
gem "apnotic", require: false
|
14
|
+
gem "lowdown", require: false
|
15
|
+
gem "andpush", require: false
|
16
|
+
gem "fcm", require: false
|
17
|
+
gem "pry"
|
18
|
+
gem "pry-byebug", platforms: :mri
|
19
|
+
|
20
|
+
gemspec path: "../"
|
@@ -0,0 +1,14 @@
|
|
1
|
+
Description:
|
2
|
+
Stubs out a new notifier and its views. Passes the notifier name, either
|
3
|
+
CamelCased or under_scored, and an optional list of push notifications
|
4
|
+
as arguments.
|
5
|
+
|
6
|
+
This generates a notifier class in app/notifiers.
|
7
|
+
|
8
|
+
Example:
|
9
|
+
rails generate pushing:notifier TweetNotifier new_mention_in_tweet
|
10
|
+
|
11
|
+
creates a TweetNotifier class and views:
|
12
|
+
Mailer: app/notifiers/tweet_notifier.rb
|
13
|
+
Views: app/views/tweet_notifier/new_mention_in_tweet.json+apn.jbuilder
|
14
|
+
app/views/tweet_notifier/new_mention_in_tweet.json+fcm.jbuilder
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Pushing
|
2
|
+
class NotifierGenerator < Rails::Generators::NamedBase
|
3
|
+
source_root File.expand_path("../templates", __FILE__)
|
4
|
+
|
5
|
+
argument :actions, type: :array, default: [], banner: "method method"
|
6
|
+
|
7
|
+
check_class_collision suffix: "Notifier"
|
8
|
+
|
9
|
+
def create_notifier_file
|
10
|
+
template "notifier.rb", File.join("app/notifiers", class_path, "#{file_name}_notifier.rb")
|
11
|
+
|
12
|
+
actions.each do |action|
|
13
|
+
template "template.json+apn.jbuilder", File.join("app/views/", "#{file_name}_notifier", "#{action}.json+apn.jbuilder")
|
14
|
+
template "template.json+fcm.jbuilder", File.join("app/views/", "#{file_name}_notifier", "#{action}.json+fcm.jbuilder")
|
15
|
+
end
|
16
|
+
|
17
|
+
in_root do
|
18
|
+
if behavior == :invoke && !File.exist?(application_notifier_file_name)
|
19
|
+
template "application_notifier.rb", application_notifier_file_name
|
20
|
+
end
|
21
|
+
|
22
|
+
if behavior == :invoke && !File.exist?(initializer_file_name)
|
23
|
+
template "initializer.rb", initializer_file_name
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def file_name # :doc:
|
31
|
+
@_file_name ||= super.gsub(/_notifier/i, "")
|
32
|
+
end
|
33
|
+
|
34
|
+
def initializer_file_name
|
35
|
+
@_initializer_file_name ||= "config/initializers/pushing.rb"
|
36
|
+
end
|
37
|
+
|
38
|
+
def application_notifier_file_name
|
39
|
+
@_application_notifier_file_name ||= if mountable_engine?
|
40
|
+
"app/notifiers/#{namespaced_path}/application_notifier.rb"
|
41
|
+
else
|
42
|
+
"app/notifiers/application_notifier.rb"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
Pushing::Platforms.configure do |config|
|
2
|
+
config.fcm.adapter = Rails.env.test? ? :test : :andpush
|
3
|
+
config.fcm.server_key = 'YOUR_FCM_TEST_SERVER_KEY'
|
4
|
+
|
5
|
+
config.apn.environment = Rails.env.production? ? :production : :development
|
6
|
+
config.apn.adapter = Rails.env.test? ? :test : :apnotic
|
7
|
+
config.apn.topic = 'com.awesomecompany.app'
|
8
|
+
config.apn.certificate_path = '/config/your_certificate.pem'
|
9
|
+
config.apn.certificate_password = 'PASSWORD_FOR_CERT'
|
10
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# In this file you can customize the APN payload. For more details about key preference, see
|
2
|
+
# Apple's offitial documentation here: http://bit.ly/apns-doc
|
3
|
+
|
4
|
+
# the JSON dictionary can include custom keys and values with your app-specific content.
|
5
|
+
# json.custom_data1 'content1...'
|
6
|
+
# json.custom_data2 'content2...'
|
7
|
+
|
8
|
+
# The aps dictionary contains the keys used by Apple to deliver the notification to the user's device.
|
9
|
+
json.aps do
|
10
|
+
|
11
|
+
# Include this key when you want the system to display a standard alert or a banner (Dictionary or String).
|
12
|
+
# json.alert 'REPLACE_WITH_ACTUAL_TITLE'
|
13
|
+
json.alert do
|
14
|
+
# A short string describing the purpose of the notification.
|
15
|
+
json.title 'REPLACE_WITH_ACTUAL_TITLE'
|
16
|
+
|
17
|
+
# The text of the alert message.
|
18
|
+
json.body 'REPLACE_WITH_ACTUAL_BODY'
|
19
|
+
|
20
|
+
# The key to a title string in the Localizable.strings file for the current localization.
|
21
|
+
# json.set! 'title-loc-key', 'key in Localizable.strings'
|
22
|
+
|
23
|
+
# Variable string values to appear in place of the format specifiers in 'title-loc-key'.
|
24
|
+
# json.set! 'title-loc-args', ['arg1', 'arg2']
|
25
|
+
|
26
|
+
# If a string is specified, the system displays an alert that includes the Close and View buttons.
|
27
|
+
# json.set! 'action-loc-key', 'key in Localizable.strings'
|
28
|
+
|
29
|
+
# A key to an alert-message string in a Localizable.strings file for the current localization.
|
30
|
+
# json.set! 'loc-key', 'key in Localizable.strings'
|
31
|
+
|
32
|
+
# Variable string values to appear in place of the format specifiers in loc-key.
|
33
|
+
# json.set! 'loc-args', ['arg1', 'arg2']
|
34
|
+
|
35
|
+
# The filename of an image file in the app bundle, with or without the filename extension.
|
36
|
+
# json.set! 'launch-image', 'your_launch_iamge.png'
|
37
|
+
end
|
38
|
+
|
39
|
+
# Include this key when you want the system to modify the badge of your app icon.
|
40
|
+
json.badge 1
|
41
|
+
|
42
|
+
# Include this key when you want the system to play a sound.
|
43
|
+
json.sound 'bingbong.aiff'
|
44
|
+
|
45
|
+
# Provide this key with a string value that represents the notification's type.
|
46
|
+
# json.category 'your-category-type', 'CATEGORY-TYPE'
|
47
|
+
|
48
|
+
# Include this key with a value of 1 to configure a silent notification.
|
49
|
+
# json.set! 'content-available', 1
|
50
|
+
|
51
|
+
# Provide this key with a string value that represents the app-specific identifier for grouping notifications.
|
52
|
+
# json.set! 'thread-id', 'THREAD-ID'
|
53
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# In this file you can customize the FCM payload. For more details about Firebase Cloud Messaging HTTP Protocol, see
|
2
|
+
# Google's offitial documentation here: https://goo.gl/tgssxy
|
3
|
+
|
4
|
+
# The recipient of a message. The value can be a device's registration token, a device group's notification key, or a
|
5
|
+
# single topic (prefixed with /topics/). Either the 'to' or 'registration_ids' key should be present.
|
6
|
+
json.to 'REPLACE_WITH_ACTUAL_REGISTRATION_ID_OR_TOPIC'
|
7
|
+
|
8
|
+
# The recipient of a multicast message, a message sent to more than one registration token. Either the 'to' or
|
9
|
+
# 'registration_ids' key should be present.
|
10
|
+
# json.registration_ids 'REPLACE_WITH_ACTUAL_REGISTRATION_IDS_OR_TOPIC'
|
11
|
+
|
12
|
+
# A logical expression of conditions that determine the message target.
|
13
|
+
# json.condition 'Topic'
|
14
|
+
|
15
|
+
# This parameter identifies a group of messages (e.g., with collapse_key:
|
16
|
+
# "Updates Available") that can be collapsed.
|
17
|
+
# json.collapse_key "Updates Available"
|
18
|
+
|
19
|
+
# Sets the priority of the message. Valid values are "normal" and "high".
|
20
|
+
# json.priority "high"
|
21
|
+
|
22
|
+
# How long (in seconds) the message should be kept in FCM storage if the device is offline.
|
23
|
+
# json.time_to_live 4.weeks.to_i
|
24
|
+
|
25
|
+
# The package name of the application where the registration tokens must match in order to receive the message.
|
26
|
+
# json.restricted_package_name 'com.yourdomain.app'
|
27
|
+
|
28
|
+
# This parameter, when set to true, allows developers to test a request without actually sending a message.
|
29
|
+
# json.dry_run true
|
30
|
+
|
31
|
+
# The custom key-value pairs of the message's payload.
|
32
|
+
# json.data do
|
33
|
+
# json.custom_data "data..."
|
34
|
+
# end
|
35
|
+
|
36
|
+
# The predefined, user-visible key-value pairs of the notification payload.
|
37
|
+
json.notification do
|
38
|
+
# The notification's title.
|
39
|
+
json.title 'REPLACE_WITH_ACTUAL_TITLE'
|
40
|
+
|
41
|
+
# The notification's body text.
|
42
|
+
json.body 'REPLACE_WITH_ACTUAL_BODY'
|
43
|
+
|
44
|
+
# The notification's channel id (new in Android O).
|
45
|
+
# json.android_channel_id "my_channel_01"
|
46
|
+
|
47
|
+
# The notification's icon.
|
48
|
+
json.icon 1
|
49
|
+
|
50
|
+
# The sound to play when the device receives the notificatio
|
51
|
+
json.sound 'default'
|
52
|
+
|
53
|
+
# Identifier used to replace existing notifications in the notification drawer.
|
54
|
+
# json.tag 'tag-name'
|
55
|
+
|
56
|
+
# The notification's icon color, expressed in #rrggbb format.
|
57
|
+
# json.color "#ffffff"
|
58
|
+
|
59
|
+
# The action associated with a user click on the notification.
|
60
|
+
# json.click_action "OPEN_ACTIVITY_1"
|
61
|
+
|
62
|
+
# The key to the body in the app's string resources to use to localize the body to the user's current localization.
|
63
|
+
# json.body_loc_key 'key in Localizable.strings'
|
64
|
+
|
65
|
+
# String values to be used in place of the format specifiers in 'body_loc_key' to use to localize the body to the
|
66
|
+
# user's current localization.
|
67
|
+
# json.body_loc_args ['arg1', 'arg2']
|
68
|
+
|
69
|
+
# The key to the title in the app's string resources to use to localize the title to the user's current localization.
|
70
|
+
# json.title_loc_key 'key in Localizable.strings'
|
71
|
+
|
72
|
+
# String values to be used in place of the format specifiers in title_loc_key to use to localize the title text to
|
73
|
+
# the user's current localization.
|
74
|
+
# json.title_loc_args ['arg1', 'arg2']
|
75
|
+
end
|
data/lib/pushing.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require "active_support/dependencies/autoload"
|
2
|
+
require "pushing/version"
|
3
|
+
|
4
|
+
module Pushing
|
5
|
+
extend ::ActiveSupport::Autoload
|
6
|
+
|
7
|
+
autoload :Adapters
|
8
|
+
autoload :Base
|
9
|
+
autoload :DeliveryJob
|
10
|
+
autoload :NotificationDelivery
|
11
|
+
autoload :Platforms
|
12
|
+
end
|
13
|
+
|
14
|
+
if defined?(Rails)
|
15
|
+
require 'pushing/railtie'
|
16
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
module Pushing
|
4
|
+
module Adapters
|
5
|
+
extend ActiveSupport::Autoload
|
6
|
+
|
7
|
+
autoload :HoustonAdapter, 'pushing/adapters/apn/houston_adapter'
|
8
|
+
autoload :ApnoticAdapter, 'pushing/adapters/apn/apnotic_adapter'
|
9
|
+
autoload :LowdownAdapter, 'pushing/adapters/apn/lowdown_adapter'
|
10
|
+
autoload :AndpushAdapter, 'pushing/adapters/fcm/andpush_adapter'
|
11
|
+
autoload :FcmGemAdapter, 'pushing/adapters/fcm/fcm_gem_adapter'
|
12
|
+
autoload :TestAdapter
|
13
|
+
|
14
|
+
# Hash object that holds referenses to adapter instances.
|
15
|
+
ADAPTER_INSTANCES = {}
|
16
|
+
|
17
|
+
# Mutex object used to ensure the +instance+ method creates a singleton object.
|
18
|
+
MUTEX = Mutex.new
|
19
|
+
|
20
|
+
private_constant :ADAPTER_INSTANCES, :MUTEX
|
21
|
+
|
22
|
+
class << self
|
23
|
+
##
|
24
|
+
# Returns the constant for the specified adapter name.
|
25
|
+
#
|
26
|
+
# Pushing::Adapters.lookup(:apnotic)
|
27
|
+
# # => Pushing::Adapters::ApnoticAdapter
|
28
|
+
def lookup(name)
|
29
|
+
const_get("#{name.to_s.camelize}Adapter")
|
30
|
+
end
|
31
|
+
|
32
|
+
##
|
33
|
+
# Provides an adapter instance specified in the +configuration+. If the adapter is not found in
|
34
|
+
# +ADAPTER_INSTANCES+, it'll look up the adapter class and create a new instance using the
|
35
|
+
# +configuration+.
|
36
|
+
def instance(configuration)
|
37
|
+
ADAPTER_INSTANCES[configuration.adapter] || MUTEX.synchronize do
|
38
|
+
ADAPTER_INSTANCES[configuration.adapter] ||= lookup(configuration.adapter).new(configuration)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
require 'apnotic'
|
4
|
+
|
5
|
+
module Pushing
|
6
|
+
module Adapters
|
7
|
+
class ApnoticAdapter
|
8
|
+
APS_DICTIONARY_KEYS = %i[
|
9
|
+
alert
|
10
|
+
badge
|
11
|
+
sound
|
12
|
+
content_available
|
13
|
+
category
|
14
|
+
url_args
|
15
|
+
mutable_content
|
16
|
+
].freeze
|
17
|
+
|
18
|
+
DEFAULT_ADAPTER_OPTIONS = {
|
19
|
+
size: Process.getrlimit(Process::RLIMIT_NOFILE).first / 8
|
20
|
+
}.freeze
|
21
|
+
|
22
|
+
attr_reader :environment, :topic, :connection_pool
|
23
|
+
|
24
|
+
def initialize(apn_settings)
|
25
|
+
@environment = apn_settings.environment.to_sym
|
26
|
+
@topic = apn_settings.topic
|
27
|
+
|
28
|
+
options = {
|
29
|
+
cert_path: apn_settings.certificate_path,
|
30
|
+
cert_pass: apn_settings.certificate_password
|
31
|
+
}
|
32
|
+
|
33
|
+
@connection_pool = {
|
34
|
+
development: Apnotic::ConnectionPool.development(options, DEFAULT_ADAPTER_OPTIONS),
|
35
|
+
production: Apnotic::ConnectionPool.new(options, DEFAULT_ADAPTER_OPTIONS),
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
def push!(notification)
|
40
|
+
message = Apnotic::Notification.new(notification.device_token)
|
41
|
+
json = notification.payload
|
42
|
+
|
43
|
+
if aps = json.delete(:aps)
|
44
|
+
APS_DICTIONARY_KEYS.each {|key| message.instance_variable_set(:"@#{key}", aps[key]) }
|
45
|
+
end
|
46
|
+
|
47
|
+
message.custom_payload = json
|
48
|
+
|
49
|
+
message.apns_id = notification.headers[:'apns-id'] || message.apns_id
|
50
|
+
message.expiration = notification.headers[:'apns-expiration'].to_i
|
51
|
+
message.priority = notification.headers[:'apns-priority']
|
52
|
+
message.topic = notification.headers[:'apns-topic'] || topic
|
53
|
+
message.apns_collapse_id = notification.headers[:'apns-collapse-id']
|
54
|
+
|
55
|
+
response = connection_pool[notification.environment || environment].with {|connection| connection.push(message) }
|
56
|
+
|
57
|
+
if !response
|
58
|
+
raise "Timeout sending a push notification"
|
59
|
+
elsif response.status != '200'
|
60
|
+
raise response.body.to_s
|
61
|
+
end
|
62
|
+
|
63
|
+
ApnResponse.new(response)
|
64
|
+
rescue => cause
|
65
|
+
response = response ? ApnResponse.new(response) : nil
|
66
|
+
error = Pushing::ApnDeliveryError.new("Error while trying to send push notification: #{cause.message}", response, notification)
|
67
|
+
|
68
|
+
raise error, error.message, cause.backtrace
|
69
|
+
end
|
70
|
+
|
71
|
+
class ApnResponse < SimpleDelegator
|
72
|
+
def code
|
73
|
+
__getobj__.status.to_i
|
74
|
+
end
|
75
|
+
alias status code
|
76
|
+
|
77
|
+
def json
|
78
|
+
@json ||= __getobj__.body.symbolize_keys if !__getobj__.body.empty?
|
79
|
+
end
|
80
|
+
alias body json
|
81
|
+
end
|
82
|
+
|
83
|
+
private_constant :ApnResponse
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|