push-core 0.0.1.pre4 → 1.0.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.
- data/README.md +62 -33
- data/bin/push +5 -5
- data/lib/push/daemon.rb +2 -2
- data/lib/push/daemon/database_reconnectable.rb +9 -7
- data/lib/push/daemon/feeder.rb +1 -1
- data/lib/push/daemon/logger.rb +22 -12
- data/lib/push/version.rb +1 -1
- data/lib/tasks/push_tasks.rake +1 -1
- metadata +10 -10
data/README.md
CHANGED
@@ -1,6 +1,12 @@
|
|
1
1
|
# Push
|
2
2
|
|
3
|
-
|
3
|
+
## Features
|
4
|
+
|
5
|
+
* Multi-App
|
6
|
+
* Multi-Provider ([APNS](https://github.com/tompesman/push-apns), [GCM](https://github.com/tompesman/push-gcm), [C2DM](https://github.com/tompesman/push-c2dm))
|
7
|
+
* Integrated feedback processing
|
8
|
+
* Rake task to cleanup the database
|
9
|
+
* Database for storage (no external dependencies)
|
4
10
|
|
5
11
|
## Installation
|
6
12
|
|
@@ -13,8 +19,8 @@ and add the push provider to you Gemfile:
|
|
13
19
|
For __APNS__ (iOS: Apple Push Notification Services):
|
14
20
|
|
15
21
|
gem push-apns
|
16
|
-
|
17
|
-
For __C2DM__ (Android: Cloud to Device Messaging, deprecated):
|
22
|
+
|
23
|
+
For __C2DM__ (Android: Cloud to Device Messaging, deprecated by Google, not this gem):
|
18
24
|
|
19
25
|
gem push-c2dm
|
20
26
|
|
@@ -33,25 +39,30 @@ To generate the migration and the configuration files run:
|
|
33
39
|
|
34
40
|
The configuration is in the database and you add the configuration per push provider with the console (`rails c`):
|
35
41
|
|
36
|
-
APNS:
|
42
|
+
APNS ([see](https://github.com/tompesman/push-core#generating-certificates)):
|
37
43
|
```ruby
|
38
|
-
Push::ConfigurationApns.create(:
|
44
|
+
Push::ConfigurationApns.create(app: 'app_name', connections: 2, enabled: true,
|
45
|
+
certificate: File.read('certificate.pem'),
|
46
|
+
feedback_poll: 60,
|
47
|
+
sandbox: false)
|
39
48
|
```
|
40
49
|
|
41
|
-
C2DM
|
50
|
+
C2DM ([see](https://developers.google.com/android/c2dm/)):
|
42
51
|
```ruby
|
43
|
-
Push::ConfigurationC2dm.create(:
|
52
|
+
Push::ConfigurationC2dm.create(app: 'app_name', connections: 2, enabled: true,
|
53
|
+
email: '<email address here>',
|
54
|
+
password: '<password here>')
|
44
55
|
```
|
45
56
|
|
46
|
-
GCM
|
57
|
+
GCM ([see](http://developer.android.com/guide/google/gcm/gs.html)):
|
47
58
|
```ruby
|
48
|
-
Push::ConfigurationGcm.create(:
|
59
|
+
Push::ConfigurationGcm.create(app: 'app_name', connections: 2, enabled: true,
|
60
|
+
key: '<api key here>')
|
49
61
|
```
|
50
62
|
|
51
|
-
You can have each provider per app_name and you can have more than one app_name. Use the instructions below to generate the certificate for the APNS provider.
|
63
|
+
You can have each provider per app_name and you can have more than one app_name. Use the instructions below to generate the certificate for the APNS provider. If you only want to prepare the database with the configurations, you can set the `enabled` switch to `false`. Only enabled configurations will be used by the daemon.
|
52
64
|
|
53
|
-
|
54
|
-
### Generating Certificates
|
65
|
+
### Generating Certificates for APNS
|
55
66
|
|
56
67
|
1. Open up Keychain Access and select the `Certificates` category in the sidebar.
|
57
68
|
2. Expand the disclosure arrow next to the iOS Push Services certificate you want to export.
|
@@ -62,60 +73,78 @@ You can have each provider per app_name and you can have more than one app_name.
|
|
62
73
|
7. Convert the certificate to a .pem, where `<environment>` should be `development` or `production`, depending on the certificate you exported.
|
63
74
|
|
64
75
|
`openssl pkcs12 -nodes -clcerts -in cert.p12 -out <environment>.pem`
|
65
|
-
|
66
|
-
8. Move the .pem file into your Rails application under `config/push`.
|
67
76
|
|
77
|
+
8. Move the .pem file somewhere where you can use the `File.read` to load the file in the database.
|
68
78
|
|
69
79
|
## Daemon
|
70
80
|
|
71
81
|
To start the daemon:
|
72
82
|
|
73
83
|
bundle exec push <environment> <options>
|
74
|
-
|
84
|
+
|
75
85
|
Where `<environment>` is your Rails environment and `<options>` can be:
|
76
86
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
87
|
+
-f, --foreground Run in the foreground. Log is not written.
|
88
|
+
-p, --pid-file PATH Path to write PID file. Relative to Rails root unless absolute.
|
89
|
+
-P, --push-poll N Frequency in seconds to check for new notifications. Default: 2.
|
90
|
+
-n, --error-notification Enables error notifications via Airbrake or Bugsnag.
|
91
|
+
-F, --feedback-poll N Frequency in seconds to check for feedback for the feedback processor. Default: 60. Use 0 to disable.
|
92
|
+
-b, --feedback-processor PATH Path to the feedback processor. Default: lib/push/feedback_processor.
|
93
|
+
-v, --version Print this version of push.
|
94
|
+
-h, --help You're looking at it.
|
85
95
|
|
86
96
|
|
87
97
|
## Sending notifications
|
88
98
|
APNS:
|
89
99
|
```ruby
|
90
|
-
Push::MessageApns.
|
100
|
+
Push::MessageApns.create(
|
101
|
+
app: 'app_name',
|
102
|
+
device: '<APNS device_token here>',
|
103
|
+
alert: 'Hello World',
|
104
|
+
sound: '1.aiff',
|
105
|
+
badge: 1,
|
106
|
+
expiry: 1.day.to_i,
|
107
|
+
attributes_for_device: {key: 'MSG'})
|
91
108
|
```
|
92
109
|
C2DM:
|
93
110
|
```ruby
|
94
|
-
Push::MessageC2dm.
|
111
|
+
Push::MessageC2dm.create(
|
112
|
+
app: 'app_name',
|
113
|
+
device: '<C2DM registration_id here>',
|
114
|
+
payload: { message: 'Hello World' },
|
115
|
+
collapse_key: 'MSG')
|
95
116
|
```
|
96
117
|
|
97
118
|
GCM:
|
98
119
|
```ruby
|
99
|
-
Push::MessageGcm.
|
120
|
+
Push::MessageGcm.create(
|
121
|
+
app: 'app_name',
|
122
|
+
device: '<GCM registration_id here>',
|
123
|
+
payload: { message: 'Hello World' },
|
124
|
+
collapse_key: 'MSG')
|
100
125
|
```
|
101
126
|
|
102
127
|
## Feedback processing
|
103
128
|
|
104
129
|
The push providers return feedback in various ways and these are captured and stored in the `push_feedback` table. The installer installs the `lib/push/feedback_processor.rb` file which is by default called every 60 seconds. In this file you can process the feedback which is different for every application.
|
105
130
|
|
106
|
-
##
|
131
|
+
## Maintenance
|
107
132
|
|
108
|
-
The push-core
|
133
|
+
The push-core comes with a rake task to delete all the messages and feedback of the last 7 days or by the DAYS parameter.
|
109
134
|
|
110
|
-
|
135
|
+
bundle exec rake push:clean DAYS=2
|
111
136
|
|
112
|
-
##
|
137
|
+
## Heroku
|
138
|
+
|
139
|
+
Push runs on Heroku with the following line in the `Procfile`.
|
113
140
|
|
114
|
-
|
115
|
-
|
141
|
+
push: bundle exec push $RACK_ENV -f
|
142
|
+
|
143
|
+
## Prerequisites
|
116
144
|
|
145
|
+
* Rails 3.2.x
|
146
|
+
* Ruby 1.9.x
|
117
147
|
|
118
148
|
## Thanks
|
119
149
|
|
120
150
|
This project started as a fork of Ian Leitch [RAPNS](https://github.com/ileitch/rapns) project. The differences between this project and RAPNS is the support for C2DM and the modularity of the push providers.
|
121
|
-
|
data/bin/push
CHANGED
@@ -5,20 +5,20 @@ require 'push'
|
|
5
5
|
|
6
6
|
environment = ARGV[0]
|
7
7
|
|
8
|
-
config = Struct.new(:foreground, :pid_file, :push_poll, :
|
8
|
+
config = Struct.new(:foreground, :pid_file, :push_poll, :error_notification, :feedback_poll, :feedback_processor).new
|
9
9
|
config.foreground = false
|
10
10
|
config.push_poll = 2
|
11
|
-
config.
|
11
|
+
config.error_notification = false
|
12
12
|
config.feedback_poll = 60
|
13
13
|
config.feedback_processor = 'lib/push/feedback_processor'
|
14
14
|
|
15
15
|
banner = 'Usage: push <Rails environment> [options]'
|
16
16
|
ARGV.options do |opts|
|
17
17
|
opts.banner = banner
|
18
|
-
opts.on('-f', '--foreground', 'Run in the foreground.') { config.foreground = true }
|
18
|
+
opts.on('-f', '--foreground', 'Run in the foreground. Log is not written.') { config.foreground = true }
|
19
19
|
opts.on('-p PATH', '--pid-file PATH', String, 'Path to write PID file. Relative to Rails root unless absolute.') { |path| config.pid_file = path }
|
20
20
|
opts.on('-P N', '--push-poll N', Integer, "Frequency in seconds to check for new notifications. Default: #{config.push_poll}.") { |n| config.push_poll = n }
|
21
|
-
opts.on('-n', '--
|
21
|
+
opts.on('-n', '--error-notification', 'Enables error notifications via Airbrake or Bugsnag.') { config.error_notification = true }
|
22
22
|
opts.on('-F N', '--feedback-poll N', Integer, "Frequency in seconds to check for feedback for the feedback processor. Default: #{config.feedback_poll}. Use 0 to disable.") { |n| config.feedback_poll = n }
|
23
23
|
opts.on('-b PATH', '--feedback-processor PATH', String, "Path to the feedback processor. Default: #{config.feedback_processor}.") { |n| config.feedback_processor = n }
|
24
24
|
opts.on('-v', '--version', 'Print this version of push.') { puts "push #{Push::VERSION}"; exit }
|
@@ -40,4 +40,4 @@ if config.pid_file && !Pathname.new(config.pid_file).absolute?
|
|
40
40
|
config.pid_file = File.join(Rails.root, config.pid_file)
|
41
41
|
end
|
42
42
|
|
43
|
-
Push::Daemon.start(
|
43
|
+
Push::Daemon.start(config)
|
data/lib/push/daemon.rb
CHANGED
@@ -19,9 +19,9 @@ module Push
|
|
19
19
|
attr_accessor :logger, :config
|
20
20
|
end
|
21
21
|
|
22
|
-
def self.start(
|
22
|
+
def self.start(config)
|
23
23
|
self.config = config
|
24
|
-
self.logger = Logger.new(:foreground => config.foreground, :
|
24
|
+
self.logger = Logger.new(:foreground => config.foreground, :error_notification => config.error_notification)
|
25
25
|
setup_signal_hooks
|
26
26
|
daemonize unless config.foreground
|
27
27
|
write_pid_file
|
@@ -1,15 +1,17 @@
|
|
1
|
-
class PGError < StandardError; end if !defined?(PGError)
|
2
|
-
module Mysql2; class Error < StandardError; end; end if !defined?(Mysql2)
|
3
|
-
|
4
1
|
module Push
|
5
2
|
module Daemon
|
6
3
|
module DatabaseReconnectable
|
7
|
-
|
4
|
+
def adaptor_errors
|
5
|
+
errors = [ActiveRecord::StatementInvalid]
|
6
|
+
errors << PGError if defined?(PGError)
|
7
|
+
errors << Mysql2::Error if defined?(Mysql2)
|
8
|
+
errors
|
9
|
+
end
|
8
10
|
|
9
11
|
def with_database_reconnect_and_retry(name)
|
10
12
|
begin
|
11
13
|
yield
|
12
|
-
rescue *
|
14
|
+
rescue *adaptor_errors => e
|
13
15
|
Push::Daemon.logger.error(e)
|
14
16
|
database_connection_lost(name)
|
15
17
|
retry
|
@@ -25,8 +27,8 @@ module Push
|
|
25
27
|
reconnect_database
|
26
28
|
check_database_is_connected
|
27
29
|
break
|
28
|
-
rescue *
|
29
|
-
Push::Daemon.logger.error(e, :
|
30
|
+
rescue *adaptor_errors => e
|
31
|
+
Push::Daemon.logger.error(e, :error_notification => false)
|
30
32
|
sleep_to_avoid_thrashing
|
31
33
|
end
|
32
34
|
end
|
data/lib/push/daemon/feeder.rb
CHANGED
@@ -28,8 +28,8 @@ module Push
|
|
28
28
|
def self.enqueue_notifications
|
29
29
|
begin
|
30
30
|
with_database_reconnect_and_retry(name) do
|
31
|
+
ready_apps = Push::Daemon::App.ready
|
31
32
|
Push::Message.ready_for_delivery.find_each do |notification|
|
32
|
-
ready_apps = Push::Daemon::App.ready
|
33
33
|
Push::Daemon::App.deliver(notification) if ready_apps.include?(notification.app)
|
34
34
|
end
|
35
35
|
end
|
data/lib/push/daemon/logger.rb
CHANGED
@@ -3,10 +3,7 @@ module Push
|
|
3
3
|
class Logger
|
4
4
|
def initialize(options)
|
5
5
|
@options = options
|
6
|
-
|
7
|
-
log_file.sync = true
|
8
|
-
@logger = ActiveSupport::BufferedLogger.new(log_file, Rails.logger.level)
|
9
|
-
@logger.auto_flushing = Rails.logger.respond_to?(:auto_flushing) ? Rails.logger.auto_flushing : true
|
6
|
+
open_log unless @options[:foreground]
|
10
7
|
end
|
11
8
|
|
12
9
|
def info(msg)
|
@@ -14,7 +11,7 @@ module Push
|
|
14
11
|
end
|
15
12
|
|
16
13
|
def error(msg, options = {})
|
17
|
-
|
14
|
+
error_notification(msg, options)
|
18
15
|
log(:error, msg, 'ERROR')
|
19
16
|
end
|
20
17
|
|
@@ -26,28 +23,41 @@ module Push
|
|
26
23
|
|
27
24
|
def log(where, msg, prefix = nil)
|
28
25
|
if msg.is_a?(Exception)
|
29
|
-
msg = "#{msg.class.name}, #{msg.message}: #{msg.backtrace.join("\n")}"
|
26
|
+
msg = "#{msg.class.name}, #{msg.message}: #{msg.backtrace.join("\n") if msg.backtrace}"
|
30
27
|
end
|
31
28
|
|
32
29
|
formatted_msg = "[#{Time.now.to_s(:db)}] "
|
33
30
|
formatted_msg << "[#{prefix}] " if prefix
|
34
31
|
formatted_msg << msg
|
35
|
-
|
36
|
-
@
|
32
|
+
|
33
|
+
if @options[:foreground]
|
34
|
+
puts formatted_msg
|
35
|
+
else
|
36
|
+
@logger.send(where, formatted_msg)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def open_log
|
41
|
+
log_file = File.open(File.join(Rails.root, 'log', 'push.log'), 'w')
|
42
|
+
log_file.sync = true
|
43
|
+
@logger = ActiveSupport::BufferedLogger.new(log_file, Rails.logger.level)
|
44
|
+
@logger.auto_flushing = Rails.logger.respond_to?(:auto_flushing) ? Rails.logger.auto_flushing : true
|
37
45
|
end
|
38
46
|
|
39
|
-
def
|
40
|
-
return unless
|
47
|
+
def error_notification(e, options)
|
48
|
+
return unless do_error_notification?(e, options)
|
41
49
|
|
42
50
|
if defined?(Airbrake)
|
43
51
|
Airbrake.notify_or_ignore(e)
|
44
52
|
elsif defined?(HoptoadNotifier)
|
45
53
|
HoptoadNotifier.notify_or_ignore(e)
|
54
|
+
elsif defined?(Bugsnag)
|
55
|
+
Bugsnag.notify(e)
|
46
56
|
end
|
47
57
|
end
|
48
58
|
|
49
|
-
def
|
50
|
-
|
59
|
+
def do_error_notification?(msg, options)
|
60
|
+
@options[:error_notification] and options[:error_notification] != false and msg.is_a?(Exception)
|
51
61
|
end
|
52
62
|
end
|
53
63
|
end
|
data/lib/push/version.rb
CHANGED
data/lib/tasks/push_tasks.rake
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
namespace :push do
|
3
3
|
desc "Delete all push messages and feedback older than 7 days or DAYS=x if defined (where x is an integer)"
|
4
4
|
task :clean => :environment do
|
5
|
-
days = ENV["DAYS"].to_i
|
5
|
+
days = ENV["DAYS"] ? ENV["DAYS"].to_i : 7
|
6
6
|
puts "Removing #{days} days of push messages and feedback"
|
7
7
|
feedback = Push::Feedback.where("created_at < ?", days.days.ago).delete_all
|
8
8
|
messages = Push::Message.where("created_at < ?", days.days.ago).delete_all
|
metadata
CHANGED
@@ -1,19 +1,19 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: push-core
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
5
|
-
prerelease:
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Tom Pesman
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-09-19 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
16
|
-
requirement: &
|
16
|
+
requirement: &70261995219020 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: 3.2.1
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70261995219020
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: sqlite3
|
27
|
-
requirement: &
|
27
|
+
requirement: &70261995218260 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,7 +32,7 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70261995218260
|
36
36
|
description: Push daemon for push notification services like APNS (iOS/Apple) and
|
37
37
|
GCM/C2DM (Android).
|
38
38
|
email:
|
@@ -85,12 +85,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
85
85
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
86
86
|
none: false
|
87
87
|
requirements:
|
88
|
-
- - ! '
|
88
|
+
- - ! '>='
|
89
89
|
- !ruby/object:Gem::Version
|
90
|
-
version:
|
90
|
+
version: '0'
|
91
91
|
requirements: []
|
92
92
|
rubyforge_project:
|
93
|
-
rubygems_version: 1.8.
|
93
|
+
rubygems_version: 1.8.17
|
94
94
|
signing_key:
|
95
95
|
specification_version: 3
|
96
96
|
summary: Core of the push daemon.
|