pushr-core 1.0.0.pre.5 → 1.0.0.rc.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3a41177751670c857d6199aa6dfca739c39b9d6a
4
- data.tar.gz: 635ffdff9092a51e835cdf042f51ab06de439fc3
3
+ metadata.gz: b5aa180695a8bae681de138ad10bacf7533aba86
4
+ data.tar.gz: 7d57cdb043ea68c5fd70203411be5929566fb07c
5
5
  SHA512:
6
- metadata.gz: 35b3e2fdae0f25fe02fc96bd7843af22bdf5ff0f83603683541da5d78a089f9a3b97a85d61a8e0683700bdaffb928903830993d11dba3a6ede588e93bfcea696
7
- data.tar.gz: 0e825ad4237f931e4054bceb9939d4b24497211c7bfdc093bce7b70c4a92103169363d1d18a02db4be65b0f8c0ce9f145bec59e8440fd39a8ab5a4f9feb322ee
6
+ metadata.gz: 3fce9432e6d7577f98eef127f4cbc038e09d6986dc7226c5f8c47a8dd90baaf5364478dd31eed989c88b2deebdaf2dca7cc57bcebd7bbf85753616cf76b9c64e
7
+ data.tar.gz: 5da4bbfb52aea12f3a68d55ad285dff059fdeaa289677e7ffea7e03fe48249369b390a1d3f65c7d1aa56a327ebf3fad62e23b35fb2aba8cd1d646b2c05e90f95
data/README.md CHANGED
@@ -11,10 +11,11 @@ want to test or contribute to this project.
11
11
 
12
12
  * Lightening fast push notification delivery
13
13
  * Redis for queueing
14
+ * Redis or YAML for configuration
14
15
  * Multi-App
15
16
  * Multi-Provider ([APNS](https://github.com/9to5/pushr-apns), [GCM](https://github.com/9to5/pushr-gcm))
16
- * Integrated feedback processing
17
17
  * Multi-process
18
+ * Integrated feedback processing
18
19
 
19
20
  ## Installation
20
21
 
@@ -36,6 +37,16 @@ And run `bundle install` to install the gems.
36
37
 
37
38
  ## Configuration
38
39
 
40
+ ### Via Redis or YAML File
41
+
42
+ The configuration of Pushr can either be stored in Redis or in a YAML file. **The default is Redis.**
43
+
44
+ If you want to use a YAML file, you need to specify it via the `-c` option of the `pushr` daemon.
45
+ Note that this will also override any existing Redis configuration.
46
+ APNS certificates can be loaded from file. If a relative file name is given, it's assumed to be relative to the same path as the YAML config file.
47
+
48
+ ### Redis
49
+
39
50
  By default the gem tries to connect to a Redis instance at localhost. If you define the `PUSHR_URL` environment variable
40
51
  it will use that. The configuration is stored in Redis and you add the configuration per push provider with the console
41
52
  (`bundle console`):
@@ -58,6 +69,7 @@ Pushr::ConfigurationApnsFeedback.new(app: 'app_name', connections: 1, enabled: t
58
69
  ```
59
70
 
60
71
  Use this configuration to let a thread check for feedback on all APNS Configurations. It checks every `feedback_poll` in seconds.
72
+ There should be only one instance of this configuration type.
61
73
 
62
74
  GCM ([see](http://developer.android.com/guide/google/gcm/gs.html)):
63
75
  ```ruby
@@ -68,6 +80,23 @@ You can have each provider per app_name and you can have more than one app_name.
68
80
  the certificate for the APNS provider. If you only want to prepare the database with the configurations, you can set the
69
81
  `enabled` switch to `false`. Only enabled configurations will be used by the daemon.
70
82
 
83
+ ### YAML File
84
+
85
+ If a YAML file is used for configuration, it needs to follow the structure of the example below, and may contain only the
86
+ desired sections. The certificates will be read from files. For security reasons, you might not want to check-in the certificate
87
+ files into your source code repository.
88
+
89
+ If no absolute path is given of the PEM files, the location is assumed to be relative to the location of the YAML file. An example
90
+ of a YAML configuration file can be found under `./lib/generators/templates/pushr.yml`.
91
+
92
+ If you are using `Pushr` with Rails, add this to your `config/initializers/pushr.rb` file:
93
+
94
+ ```ruby
95
+ Pushr::Core.configure do |config|
96
+ config.configuration_file = File.join(Rails.root , 'config/pushr/config.yaml')
97
+ end
98
+ ```
99
+
71
100
  ### Generating Certificates for APNS
72
101
 
73
102
  1. Open up Keychain Access and select the `Certificates` category in the sidebar.
@@ -92,9 +121,14 @@ To start the daemon:
92
121
  Where `<options>` can be:
93
122
 
94
123
  -f, --foreground Run in the foreground. Log is not written.
95
- -p, --pid-file PATH Path to write PID file. Relative to Rails root unless absolute.
96
- -b, --feedback-processor PATH Path to the feedback processor. Default: lib/push/feedback_processor.
97
- -v, --version Print this version of push.
124
+ -c, --configuration FILE Read the configuration from this YAML file
125
+ -o, --redis-host HOST Hostname of redis instance
126
+ -r, --redis-port PORT Port of redis instance
127
+ -n, --redis-namespace NAMESPACE Namespace on redis connection
128
+ -p, --pid-file PATH Path to write PID file. Relative to current directory unless absolute.
129
+ -b, --feedback-processor PATH Path to the feedback processor. Default: none. Example: 'lib/pushr/feedback_processor'
130
+ -s, --stats-processor PATH Path to the stats processor. Default: none. Example: 'lib/pushr/stats_processor'
131
+ -v, --version Print this version of pushr.
98
132
  -h, --help You're looking at it.
99
133
 
100
134
  ## Sending notifications
@@ -113,6 +147,24 @@ Pushr::MessageApns.new(
113
147
  content_available: 1).save
114
148
  ```
115
149
 
150
+
151
+ Silent Push Notification via APNS:
152
+
153
+ ```ruby
154
+ Push::MessageApns.create(
155
+ app: 'app_name',
156
+ device: '<APNS device_token here>',
157
+ alert: nil,
158
+ sound: nil,
159
+ badge: 1,
160
+ content_available: 1, # see footnote
161
+ expiry: 1.day.to_i,
162
+ attributes_for_device: nil)
163
+ ```
164
+
165
+ Use `content_available: 1` if the iOS device should start your app upon receiving the silent push notification.
166
+
167
+
116
168
  GCM:
117
169
  ```ruby
118
170
  Pushr::MessageGcm.new(
@@ -130,9 +182,30 @@ Pushr::MessageGcm.new(
130
182
  ## Feedback processing
131
183
 
132
184
  The push providers return feedback in various ways and these are captured and stored in the `push_feedback` table. The
133
- installer installs the `lib/push/feedback_processor.rb` file which is by default called every 60 seconds. In this file
185
+ installer installs the `lib/pushr/feedback_processor.rb` file which is by default called every 60 seconds. In this file
134
186
  you can process the feedback which is different for every application.
135
187
 
188
+ ## Tracking your own Message IDs
189
+
190
+ If you have your own message-IDs for notifications in your system and want to track them throughout the message delivery, so they
191
+ show up in all the logs you can add this during message creation:
192
+ ```ruby
193
+ external_id: your_external_id_here
194
+ ```
195
+
196
+ You can also set the prefix under which your message ID will show up in the logs:
197
+ ```ruby
198
+ Pushr::Core.configure do |config|
199
+ config.external_id_tag = 'MyID' # will pre-fix the above message ID with this string
200
+ end
201
+ ```
202
+
203
+ This can be useful if you want to automatically ingest your log files for analytics.
204
+
205
+ Furthermore you can hand your message-ID to the mobile device, so it can either log it, or the mobile device can return a call to
206
+ an API endpoint to record the time the message was actually received. This way you can measure end-to-end delivery times. This
207
+ works best for silent push notifications in APNS.
208
+
136
209
  ## Heroku
137
210
 
138
211
  Push runs on Heroku with the following line in the `Procfile`.
data/bin/pushr CHANGED
@@ -9,6 +9,7 @@ require 'pushr/daemon'
9
9
 
10
10
  settings = Pushr::Daemon::Settings.new
11
11
  banner = 'Usage: pushr [options]'
12
+ connection = {}
12
13
  ARGV.options do |opts|
13
14
  opts.banner = banner
14
15
 
@@ -16,6 +17,22 @@ ARGV.options do |opts|
16
17
  settings.foreground = true
17
18
  end
18
19
 
20
+ opts.on('-c FILE', '--configuration FILE', 'Read the configuration from this YAML file') do |file|
21
+ Pushr::Core.configuration_file = file
22
+ end
23
+
24
+ opts.on('-o HOST', '--redis-host HOST', String, 'Hostname of redis instance') do |host|
25
+ connection[:host] = host
26
+ end
27
+
28
+ opts.on('-r PORT', '--redis-port PORT', String, 'Port of redis instance') do |port|
29
+ connection[:port] = port
30
+ end
31
+
32
+ opts.on('-n NAMESPACE', '--redis-namespace NAMESPACE', String, 'Namespace on redis connection') do |namespace|
33
+ connection[:namespace] = namespace
34
+ end
35
+
19
36
  opts.on('-p PATH', '--pid-file PATH', String,
20
37
  'Path to write PID file. Relative to current directory unless absolute.') do |path|
21
38
  settings.pid_file = path
@@ -44,6 +61,13 @@ ARGV.options do |opts|
44
61
  opts.parse!
45
62
  end
46
63
 
64
+ Pushr::Core.configure do |config|
65
+ x = { }
66
+ x[:namespace] = connection[:namespace] if connection[:namespace]
67
+ x[:url] = "redis://#{connection[:host]}:#{connection[:port] || 6379}/0" if connection[:host]
68
+ config.redis = x
69
+ end
70
+
47
71
  if ENV['AIRBRAKE_API_KEY']
48
72
  require 'airbrake'
49
73
  settings.error_notification = true
@@ -0,0 +1,53 @@
1
+ # Configurations for all Pushr Gems
2
+ ---
3
+
4
+ # GCM (Android Messaging):
5
+ - type: Pushr::ConfigurationGcm
6
+ app: gcm-development
7
+ enabled: true
8
+ connections: 1
9
+ api: your-api-key-here
10
+
11
+ # APNS (Apple Messaging):
12
+ - type: Pushr::ConfigurationApns
13
+ app: ios-development
14
+ enabled: true
15
+ connections: 1
16
+ skip_check_for_error: false
17
+ sandbox: true
18
+ certificate: filename.pem
19
+
20
+ - type: Pushr::ConfigurationApns
21
+ app: ios-development
22
+ enabled: true
23
+ connections: 1
24
+ sandbox: true
25
+ certificate: pushr/ios-development.pem
26
+
27
+ - type: Pushr::ConfigurationApns
28
+ app: ios-production
29
+ enabled: true
30
+ connections: 1
31
+ sandbox: false
32
+ certificate: pushr/ios-production.pem
33
+
34
+ - type: Pushr::ConfigurationApns
35
+ app: ios-beta
36
+ enabled: true
37
+ connections: 1
38
+ sandbox: true
39
+ certificate: pushr/ios-beta.pem
40
+
41
+ - type: Pushr::ConfigurationApns
42
+ app: ios-enterprise
43
+ enabled: true
44
+ connections: 1
45
+ sandbox: false
46
+ certificate: pushr/ios-enterprise.pem
47
+
48
+ # APNS Feedback (Feedback Service for all APNS Connections):
49
+ - type: Pushr::ConfigurationApnsFeedback
50
+ app: apns-feedback
51
+ enabled: true
52
+ connections: 1
53
+ feedback_poll: 300 # seconds
@@ -2,6 +2,7 @@ module Pushr
2
2
  class Configuration
3
3
  include ActiveModel::Validations
4
4
 
5
+ attr_accessor :id, :type, :app, :enabled, :connections
5
6
  validates :app, presence: true
6
7
  validates :connections, presence: true
7
8
  validates :connections, numericality: { greater_than: 0, only_integer: true }
@@ -30,19 +31,40 @@ module Pushr
30
31
  Pushr::Core.redis { |conn| conn.hdel('pushr:configurations', key) }
31
32
  end
32
33
 
34
+ def to_json
35
+ MultiJson.dump(to_hash)
36
+ end
37
+
33
38
  def self.all
34
- configurations = Pushr::Core.redis { |conn| conn.hgetall('pushr:configurations') }
35
- configurations.each { |key, config| configurations[key] = instantiate(config, key) }
36
- configurations.values
39
+ if Pushr::Core.configuration_file # only set if file exists
40
+ read_from_yaml_file
41
+ else
42
+ read_from_redis
43
+ end
37
44
  end
38
45
 
39
46
  def self.find(key)
40
47
  config = Pushr::Core.redis { |conn| conn.hget('pushr:configurations', key) }
41
- instantiate(config, key)
48
+ instantiate_json(config, key)
49
+ end
50
+
51
+ def self.read_from_yaml_file
52
+ filename = Pushr::Core.configuration_file
53
+ configs = File.open(filename) { |fd| YAML.load(fd) }
54
+ configs.map { |hsh| instantiate(hsh) }
55
+ end
56
+
57
+ def self.read_from_redis
58
+ configurations = Pushr::Core.redis { |conn| conn.hgetall('pushr:configurations') }
59
+ configurations.each { |key, config| configurations[key] = instantiate_json(config, key) }
60
+ configurations.values
61
+ end
62
+
63
+ def self.instantiate_json(config, id)
64
+ instantiate(::MultiJson.load(config).merge!(id: id))
42
65
  end
43
66
 
44
- def self.instantiate(config, id)
45
- hsh = ::MultiJson.load(config).merge!(id: id)
67
+ def self.instantiate(hsh)
46
68
  klass = hsh['type'].split('::').reduce(Object) { |a, e| a.const_get e }
47
69
  klass.new(hsh)
48
70
  end
data/lib/pushr/core.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require 'yaml'
1
2
  require 'active_model'
2
3
  require 'multi_json'
3
4
  require 'pushr/version'
@@ -9,7 +10,7 @@ require 'pushr/redis_connection'
9
10
  module Pushr
10
11
  module Core
11
12
  NAME = 'Pushr'
12
- DEFAULTS = {}
13
+ DEFAULTS = { external_id_tag: 'external_id' }
13
14
 
14
15
  attr_writer :options
15
16
 
@@ -20,8 +21,8 @@ module Pushr
20
21
  ##
21
22
  # Configuration for Pushr, use like:
22
23
  #
23
- # Pushr.configure do |config|
24
- # config.redis = { :namespace => 'myapp', :size => 1, :url => 'redis://myhost:8877/mydb' }
24
+ # Pushr::Core.configure do |config|
25
+ # config.redis = { namespace: 'myapp', url: 'redis://myhost:8877/mydb' }
25
26
  # end
26
27
  def self.configure
27
28
  yield self
@@ -35,8 +36,8 @@ module Pushr
35
36
 
36
37
  def self.redis=(hash)
37
38
  if hash.is_a?(Hash)
38
- @redis = RedisConnection.create(hash)
39
- options[:namespace] ||= hash[:namespace]
39
+ options.merge!(hash)
40
+ @redis = RedisConnection.create(options)
40
41
  elsif hash.is_a?(ConnectionPool)
41
42
  @redis = hash
42
43
  else
@@ -44,8 +45,31 @@ module Pushr
44
45
  end
45
46
  end
46
47
 
48
+ def self.external_id_tag=(value)
49
+ options[:external_id_tag] = value
50
+ end
51
+
52
+ def self.external_id_tag
53
+ options[:external_id_tag]
54
+ end
55
+
56
+ def self.configuration_file
57
+ options[:configuration_file]
58
+ end
59
+
60
+ def self.configuration_file=(filename)
61
+ if filename
62
+ filename = File.join(Dir.pwd, filename) unless Pathname.new(filename).absolute?
63
+ if File.file?(filename)
64
+ options[:configuration_file] = filename
65
+ else
66
+ fail ArgumentError, "can not find config file: #{filename}"
67
+ end
68
+ end
69
+ end
70
+
47
71
  # instruments with a block
48
- def self.instrument(name, payload = {}, &block)
72
+ def self.instrument(name, payload = {})
49
73
  ActiveSupport::Notifications.instrument(name, payload) do
50
74
  yield
51
75
  end
@@ -7,7 +7,7 @@ module Pushr
7
7
  attr_reader :apps
8
8
 
9
9
  def load
10
- @apps = Configuration.all.keep_if { |c| c.enabled == true }.map { |c| App.new(c) }
10
+ @apps = Pushr::Configuration.all.keep_if { |c| c.enabled == true }.map { |c| App.new(c) }
11
11
  end
12
12
 
13
13
  def total_connections
@@ -1,6 +1,8 @@
1
1
  module Pushr
2
2
  class Feedback
3
3
  include ActiveModel::Validations
4
+
5
+ attr_accessor :type, :app
4
6
  validates :app, presence: true
5
7
  validates :device, presence: true
6
8
  validates :follow_up, presence: true
@@ -21,6 +23,10 @@ module Pushr
21
23
  end
22
24
  end
23
25
 
26
+ def to_json
27
+ MultiJson.dump(to_hash)
28
+ end
29
+
24
30
  def self.next(timeout = 3)
25
31
  Pushr::Core.redis do |conn|
26
32
  feedback = conn.blpop('pushr:feedback', timeout: timeout)
data/lib/pushr/message.rb CHANGED
@@ -2,6 +2,7 @@ module Pushr
2
2
  class Message
3
3
  include ActiveModel::Validations
4
4
 
5
+ attr_accessor :type, :app, :external_id
5
6
  validates :app, presence: true
6
7
 
7
8
  def initialize(attributes = {})
@@ -19,6 +20,10 @@ module Pushr
19
20
  end
20
21
  end
21
22
 
23
+ def to_json
24
+ MultiJson.dump(to_hash)
25
+ end
26
+
22
27
  def self.next(queue_name, timeout = 3)
23
28
  Pushr::Core.redis do |conn|
24
29
  message = conn.blpop(queue_name, timeout: timeout)
@@ -8,7 +8,6 @@ module Pushr
8
8
  url = options[:url] || determine_redis_provider || 'redis://localhost:6379/0'
9
9
  driver = options[:driver] || 'ruby'
10
10
  # need a connection for Fetcher and Retry
11
- # size = options[:size] || (Pushr.server? ? (Pushr.options[:concurrency] + 2) : 5)
12
11
  size = options[:size] || 5
13
12
  namespace = options[:namespace] || Pushr::Core.options[:namespace]
14
13
 
data/lib/pushr/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Pushr
2
- VERSION = '1.0.0.pre.5'
2
+ VERSION = '1.0.0.rc.1'
3
3
  end
@@ -13,8 +13,10 @@ describe Pushr::Daemon::App do
13
13
  allow(logger).to receive(:error)
14
14
  allow(logger).to receive(:warn)
15
15
  Pushr::Daemon.logger = logger
16
+ Pushr::Daemon.config = settings
16
17
  end
17
18
 
19
+ let(:settings) { Pushr::Daemon::Settings.new }
18
20
  let(:config) { Pushr::ConfigurationDummy.new(app: 'app_name', connections: 1, enabled: true) }
19
21
  describe 'self' do
20
22
  before(:each) do
@@ -1,22 +1,13 @@
1
1
  module Pushr
2
2
  class ConfigurationDummy < Pushr::Configuration
3
- attr_accessor :id, :type, :app, :enabled, :connections, :test_attr
3
+ attr_accessor :test_attr
4
4
 
5
5
  def name
6
6
  :dummy
7
7
  end
8
8
 
9
- def to_json(args = nil)
10
- hsh = {
11
- id: [@app, name].join(':'),
12
- type: self.class.to_s,
13
- app: app,
14
- enabled: enabled,
15
- connections: connections,
16
- test_attr: test_attr
17
- }
18
-
19
- ::MultiJson.dump(hsh)
9
+ def to_hash(_ = nil)
10
+ { id: [@app, name].join(':'), type: self.class.to_s, app: app, enabled: enabled, connections: connections, test_attr: test_attr }
20
11
  end
21
12
  end
22
13
  end
@@ -13,7 +13,7 @@ module Pushr
13
13
  connection = DummySupport::ConnectionDummy.new(configuration, i + 1)
14
14
  connection.connect
15
15
 
16
- handler = MessageHandler.new("pushr:#{configuration.app}:#{configuration.name}", connection, configuration.app, i + 1)
16
+ handler = MessageHandler.new("pushr:#{configuration.key}", connection, configuration.app, i + 1)
17
17
  handler.start
18
18
  @handlers << handler
19
19
  end
@@ -1,11 +1,11 @@
1
1
  module Pushr
2
2
  class FeedbackDummy < Pushr::Feedback
3
- attr_accessor :type, :app, :device, :follow_up, :failed_at
3
+ attr_accessor :device, :follow_up, :failed_at
4
4
  validates :device, format: { with: /\A[a-z0-9]{64}\z/ }
5
5
  validates :follow_up, inclusion: { in: %w(delete), message: '%{value} is not a valid follow-up' }
6
6
 
7
- def to_json(args = nil)
8
- MultiJson.dump(type: 'Pushr::FeedbackDummy', app: app, device: device, follow_up: follow_up, failed_at: failed_at)
7
+ def to_hash(_ = nil)
8
+ { type: 'Pushr::FeedbackDummy', app: app, device: device, follow_up: follow_up, failed_at: failed_at }
9
9
  end
10
10
  end
11
11
  end
@@ -4,5 +4,9 @@ module Pushr
4
4
  def name
5
5
  :invalid_dummy
6
6
  end
7
+
8
+ def to_hash
9
+ { type: self.class.to_s, app: app, enabled: enabled, connections: connections, test_attr: test_attr }
10
+ end
7
11
  end
8
12
  end
@@ -1,13 +1,13 @@
1
1
  module Pushr
2
2
  class MessageDummy < Pushr::Message
3
3
  POSTFIX = 'dummy'
4
- attr_accessor :postfix, :type, :app
4
+ attr_accessor :device_id
5
5
 
6
6
  def to_message
7
7
  end
8
8
 
9
- def to_json(args = nil)
10
- MultiJson.dump(type: self.class.to_s, app: app)
9
+ def to_hash(_ = nil)
10
+ { type: self.class.to_s, app: app, device_id: device_id }
11
11
  end
12
12
  end
13
13
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pushr-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.pre.5
4
+ version: 1.0.0.rc.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tom Pesman
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-06-12 00:00:00.000000000 Z
11
+ date: 2014-06-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis
@@ -202,6 +202,7 @@ extensions: []
202
202
  extra_rdoc_files: []
203
203
  files:
204
204
  - lib/generators/templates/feedback_processor.rb
205
+ - lib/generators/templates/pushr.yml
205
206
  - lib/pushr/configuration.rb
206
207
  - lib/pushr/core.rb
207
208
  - lib/pushr/daemon.rb