rpush 1.0.0-java
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +99 -0
- data/LICENSE +7 -0
- data/README.md +189 -0
- data/bin/rpush +36 -0
- data/config/database.yml +44 -0
- data/lib/generators/rpush_generator.rb +44 -0
- data/lib/generators/templates/add_adm.rb +23 -0
- data/lib/generators/templates/add_alert_is_json_to_rapns_notifications.rb +9 -0
- data/lib/generators/templates/add_app_to_rapns.rb +11 -0
- data/lib/generators/templates/add_fail_after_to_rpush_notifications.rb +9 -0
- data/lib/generators/templates/add_gcm.rb +102 -0
- data/lib/generators/templates/add_rpush.rb +349 -0
- data/lib/generators/templates/add_wpns.rb +16 -0
- data/lib/generators/templates/create_rapns_apps.rb +16 -0
- data/lib/generators/templates/create_rapns_feedback.rb +18 -0
- data/lib/generators/templates/create_rapns_notifications.rb +29 -0
- data/lib/generators/templates/rename_rapns_to_rpush.rb +63 -0
- data/lib/generators/templates/rpush.rb +104 -0
- data/lib/rpush/TODO +3 -0
- data/lib/rpush/adm/app.rb +15 -0
- data/lib/rpush/adm/data_validator.rb +11 -0
- data/lib/rpush/adm/notification.rb +29 -0
- data/lib/rpush/apns/app.rb +29 -0
- data/lib/rpush/apns/binary_notification_validator.rb +12 -0
- data/lib/rpush/apns/device_token_format_validator.rb +12 -0
- data/lib/rpush/apns/feedback.rb +16 -0
- data/lib/rpush/apns/notification.rb +84 -0
- data/lib/rpush/apns_feedback.rb +13 -0
- data/lib/rpush/app.rb +18 -0
- data/lib/rpush/configuration.rb +75 -0
- data/lib/rpush/daemon/adm/delivery.rb +222 -0
- data/lib/rpush/daemon/adm.rb +9 -0
- data/lib/rpush/daemon/apns/certificate_expired_error.rb +20 -0
- data/lib/rpush/daemon/apns/delivery.rb +64 -0
- data/lib/rpush/daemon/apns/disconnection_error.rb +20 -0
- data/lib/rpush/daemon/apns/feedback_receiver.rb +79 -0
- data/lib/rpush/daemon/apns.rb +16 -0
- data/lib/rpush/daemon/app_runner.rb +187 -0
- data/lib/rpush/daemon/batch.rb +115 -0
- data/lib/rpush/daemon/constants.rb +59 -0
- data/lib/rpush/daemon/delivery.rb +28 -0
- data/lib/rpush/daemon/delivery_error.rb +19 -0
- data/lib/rpush/daemon/dispatcher/http.rb +21 -0
- data/lib/rpush/daemon/dispatcher/tcp.rb +30 -0
- data/lib/rpush/daemon/dispatcher_loop.rb +54 -0
- data/lib/rpush/daemon/dispatcher_loop_collection.rb +33 -0
- data/lib/rpush/daemon/feeder.rb +68 -0
- data/lib/rpush/daemon/gcm/delivery.rb +222 -0
- data/lib/rpush/daemon/gcm.rb +9 -0
- data/lib/rpush/daemon/interruptible_sleep.rb +61 -0
- data/lib/rpush/daemon/loggable.rb +31 -0
- data/lib/rpush/daemon/reflectable.rb +13 -0
- data/lib/rpush/daemon/retry_header_parser.rb +23 -0
- data/lib/rpush/daemon/retryable_error.rb +20 -0
- data/lib/rpush/daemon/service_config_methods.rb +33 -0
- data/lib/rpush/daemon/store/active_record/reconnectable.rb +68 -0
- data/lib/rpush/daemon/store/active_record.rb +154 -0
- data/lib/rpush/daemon/tcp_connection.rb +143 -0
- data/lib/rpush/daemon/too_many_requests_error.rb +20 -0
- data/lib/rpush/daemon/wpns/delivery.rb +132 -0
- data/lib/rpush/daemon/wpns.rb +9 -0
- data/lib/rpush/daemon.rb +140 -0
- data/lib/rpush/deprecatable.rb +23 -0
- data/lib/rpush/deprecation.rb +23 -0
- data/lib/rpush/embed.rb +28 -0
- data/lib/rpush/gcm/app.rb +11 -0
- data/lib/rpush/gcm/expiry_collapse_key_mutual_inclusion_validator.rb +11 -0
- data/lib/rpush/gcm/notification.rb +30 -0
- data/lib/rpush/logger.rb +63 -0
- data/lib/rpush/multi_json_helper.rb +16 -0
- data/lib/rpush/notification.rb +69 -0
- data/lib/rpush/notifier.rb +52 -0
- data/lib/rpush/payload_data_size_validator.rb +10 -0
- data/lib/rpush/push.rb +16 -0
- data/lib/rpush/railtie.rb +11 -0
- data/lib/rpush/reflection.rb +58 -0
- data/lib/rpush/registration_ids_count_validator.rb +10 -0
- data/lib/rpush/version.rb +3 -0
- data/lib/rpush/wpns/app.rb +9 -0
- data/lib/rpush/wpns/notification.rb +26 -0
- data/lib/rpush.rb +62 -0
- data/lib/tasks/cane.rake +18 -0
- data/lib/tasks/rpush.rake +16 -0
- data/lib/tasks/test.rake +38 -0
- data/spec/functional/adm_spec.rb +43 -0
- data/spec/functional/apns_spec.rb +58 -0
- data/spec/functional/embed_spec.rb +49 -0
- data/spec/functional/gcm_spec.rb +42 -0
- data/spec/functional/wpns_spec.rb +41 -0
- data/spec/support/cert_with_password.pem +90 -0
- data/spec/support/cert_without_password.pem +59 -0
- data/spec/support/install.sh +32 -0
- data/spec/support/simplecov_helper.rb +20 -0
- data/spec/support/simplecov_quality_formatter.rb +8 -0
- data/spec/tmp/.gitkeep +0 -0
- data/spec/unit/adm/app_spec.rb +58 -0
- data/spec/unit/adm/notification_spec.rb +45 -0
- data/spec/unit/apns/app_spec.rb +29 -0
- data/spec/unit/apns/feedback_spec.rb +9 -0
- data/spec/unit/apns/notification_spec.rb +208 -0
- data/spec/unit/apns_feedback_spec.rb +21 -0
- data/spec/unit/app_spec.rb +30 -0
- data/spec/unit/configuration_spec.rb +45 -0
- data/spec/unit/daemon/adm/delivery_spec.rb +243 -0
- data/spec/unit/daemon/apns/certificate_expired_error_spec.rb +11 -0
- data/spec/unit/daemon/apns/delivery_spec.rb +101 -0
- data/spec/unit/daemon/apns/disconnection_error_spec.rb +18 -0
- data/spec/unit/daemon/apns/feedback_receiver_spec.rb +117 -0
- data/spec/unit/daemon/app_runner_spec.rb +292 -0
- data/spec/unit/daemon/batch_spec.rb +232 -0
- data/spec/unit/daemon/delivery_error_spec.rb +13 -0
- data/spec/unit/daemon/delivery_spec.rb +38 -0
- data/spec/unit/daemon/dispatcher/http_spec.rb +33 -0
- data/spec/unit/daemon/dispatcher/tcp_spec.rb +38 -0
- data/spec/unit/daemon/dispatcher_loop_collection_spec.rb +37 -0
- data/spec/unit/daemon/dispatcher_loop_spec.rb +71 -0
- data/spec/unit/daemon/feeder_spec.rb +98 -0
- data/spec/unit/daemon/gcm/delivery_spec.rb +310 -0
- data/spec/unit/daemon/interruptible_sleep_spec.rb +68 -0
- data/spec/unit/daemon/reflectable_spec.rb +27 -0
- data/spec/unit/daemon/retryable_error_spec.rb +14 -0
- data/spec/unit/daemon/service_config_methods_spec.rb +33 -0
- data/spec/unit/daemon/store/active_record/reconnectable_spec.rb +114 -0
- data/spec/unit/daemon/store/active_record_spec.rb +357 -0
- data/spec/unit/daemon/tcp_connection_spec.rb +287 -0
- data/spec/unit/daemon/too_many_requests_error_spec.rb +14 -0
- data/spec/unit/daemon/wpns/delivery_spec.rb +159 -0
- data/spec/unit/daemon_spec.rb +159 -0
- data/spec/unit/deprecatable_spec.rb +32 -0
- data/spec/unit/deprecation_spec.rb +15 -0
- data/spec/unit/embed_spec.rb +50 -0
- data/spec/unit/gcm/app_spec.rb +4 -0
- data/spec/unit/gcm/notification_spec.rb +36 -0
- data/spec/unit/logger_spec.rb +127 -0
- data/spec/unit/notification_shared.rb +105 -0
- data/spec/unit/notification_spec.rb +15 -0
- data/spec/unit/notifier_spec.rb +49 -0
- data/spec/unit/push_spec.rb +43 -0
- data/spec/unit/reflection_spec.rb +30 -0
- data/spec/unit/rpush_spec.rb +9 -0
- data/spec/unit/wpns/app_spec.rb +4 -0
- data/spec/unit/wpns/notification_spec.rb +30 -0
- data/spec/unit_spec_helper.rb +101 -0
- metadata +304 -0
@@ -0,0 +1,143 @@
|
|
1
|
+
module Rpush
|
2
|
+
module Daemon
|
3
|
+
class TcpConnectionError < StandardError; end
|
4
|
+
|
5
|
+
class TcpConnection
|
6
|
+
include Reflectable
|
7
|
+
include Loggable
|
8
|
+
|
9
|
+
attr_accessor :last_write
|
10
|
+
|
11
|
+
def self.idle_period
|
12
|
+
30.minutes
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(app, host, port)
|
16
|
+
@app = app
|
17
|
+
@host = host
|
18
|
+
@port = port
|
19
|
+
@certificate = app.certificate
|
20
|
+
@password = app.password
|
21
|
+
written
|
22
|
+
end
|
23
|
+
|
24
|
+
def connect
|
25
|
+
@ssl_context = setup_ssl_context
|
26
|
+
@tcp_socket, @ssl_socket = connect_socket
|
27
|
+
end
|
28
|
+
|
29
|
+
def close
|
30
|
+
begin
|
31
|
+
@ssl_socket.close if @ssl_socket
|
32
|
+
@tcp_socket.close if @tcp_socket
|
33
|
+
rescue IOError
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def read(num_bytes)
|
38
|
+
@ssl_socket.read(num_bytes)
|
39
|
+
end
|
40
|
+
|
41
|
+
def select(timeout)
|
42
|
+
IO.select([@ssl_socket], nil, nil, timeout)
|
43
|
+
end
|
44
|
+
|
45
|
+
def write(data)
|
46
|
+
reconnect_idle if idle_period_exceeded?
|
47
|
+
|
48
|
+
retry_count = 0
|
49
|
+
|
50
|
+
begin
|
51
|
+
write_data(data)
|
52
|
+
rescue Errno::EPIPE, Errno::ETIMEDOUT, OpenSSL::SSL::SSLError, IOError => e
|
53
|
+
retry_count += 1;
|
54
|
+
|
55
|
+
if retry_count == 1
|
56
|
+
log_error("Lost connection to #{@host}:#{@port} (#{e.class.name}), reconnecting...")
|
57
|
+
reflect(:apns_connection_lost, @app, e) # deprecated
|
58
|
+
reflect(:tcp_connection_lost, @app, e)
|
59
|
+
end
|
60
|
+
|
61
|
+
if retry_count <= 3
|
62
|
+
reconnect
|
63
|
+
sleep 1
|
64
|
+
retry
|
65
|
+
else
|
66
|
+
raise TcpConnectionError, "#{@app.name} tried #{retry_count-1} times to reconnect but failed (#{e.class.name})."
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def reconnect
|
72
|
+
close
|
73
|
+
@tcp_socket, @ssl_socket = connect_socket
|
74
|
+
end
|
75
|
+
|
76
|
+
protected
|
77
|
+
|
78
|
+
def reconnect_idle
|
79
|
+
log_info("Idle period exceeded, reconnecting...")
|
80
|
+
reconnect
|
81
|
+
end
|
82
|
+
|
83
|
+
def idle_period_exceeded?
|
84
|
+
Time.now - last_write > self.class.idle_period
|
85
|
+
end
|
86
|
+
|
87
|
+
def write_data(data)
|
88
|
+
@ssl_socket.write(data)
|
89
|
+
@ssl_socket.flush
|
90
|
+
written
|
91
|
+
end
|
92
|
+
|
93
|
+
def written
|
94
|
+
self.last_write = Time.now
|
95
|
+
end
|
96
|
+
|
97
|
+
def setup_ssl_context
|
98
|
+
ssl_context = OpenSSL::SSL::SSLContext.new
|
99
|
+
ssl_context.key = OpenSSL::PKey::RSA.new(@certificate, @password)
|
100
|
+
ssl_context.cert = OpenSSL::X509::Certificate.new(@certificate)
|
101
|
+
ssl_context
|
102
|
+
end
|
103
|
+
|
104
|
+
def connect_socket
|
105
|
+
check_certificate_expiration
|
106
|
+
|
107
|
+
tcp_socket = TCPSocket.new(@host, @port)
|
108
|
+
tcp_socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, 1)
|
109
|
+
tcp_socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
110
|
+
ssl_socket = OpenSSL::SSL::SSLSocket.new(tcp_socket, @ssl_context)
|
111
|
+
ssl_socket.sync = true
|
112
|
+
ssl_socket.connect
|
113
|
+
log_info("Connected to #{@host}:#{@port}")
|
114
|
+
[tcp_socket, ssl_socket]
|
115
|
+
end
|
116
|
+
|
117
|
+
def check_certificate_expiration
|
118
|
+
cert = @ssl_context.cert
|
119
|
+
if certificate_expired?
|
120
|
+
log_error(certificate_msg('expired'))
|
121
|
+
raise Rpush::Apns::CertificateExpiredError.new(@app, cert.not_after)
|
122
|
+
elsif certificate_expires_soon?
|
123
|
+
log_warn(certificate_msg('will expire'))
|
124
|
+
reflect(:apns_certificate_will_expire, @app, cert.not_after) # deprecated
|
125
|
+
reflect(:ssl_certificate_will_expire, @app, cert.not_after)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def certificate_msg(msg)
|
130
|
+
time = @ssl_context.cert.not_after.utc.strftime("%Y-%m-%d %H:%M:%S UTC")
|
131
|
+
"Certificate #{msg} at #{time}."
|
132
|
+
end
|
133
|
+
|
134
|
+
def certificate_expired?
|
135
|
+
@ssl_context.cert.not_after && @ssl_context.cert.not_after.utc < Time.now.utc
|
136
|
+
end
|
137
|
+
|
138
|
+
def certificate_expires_soon?
|
139
|
+
@ssl_context.cert.not_after && @ssl_context.cert.not_after.utc < (Time.now + 1.month).utc
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Rpush
|
2
|
+
class TooManyRequestsError < StandardError
|
3
|
+
attr_reader :code, :description, :response
|
4
|
+
|
5
|
+
def initialize(code, notification_id, description, response)
|
6
|
+
@code = code
|
7
|
+
@notification_id = notification_id
|
8
|
+
@description = description
|
9
|
+
@response = response
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_s
|
13
|
+
message
|
14
|
+
end
|
15
|
+
|
16
|
+
def message
|
17
|
+
"Too many requests for #{@notification_id}, received error #{@code} (#{@description}) - retry after #{@response.header['retry-after']}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
module Rpush
|
2
|
+
module Daemon
|
3
|
+
module Wpns
|
4
|
+
|
5
|
+
# http://msdn.microsoft.com/en-us/library/windowsphone/develop/ff941100%28v=vs.105%29.aspx
|
6
|
+
class Delivery < Rpush::Daemon::Delivery
|
7
|
+
|
8
|
+
FAILURE_MESSAGES = {
|
9
|
+
400 => 'Bad XML or malformed notification URI.',
|
10
|
+
401 => 'Unauthorized to send a notification to this app.'
|
11
|
+
}
|
12
|
+
|
13
|
+
def initialize(app, http, notification, batch)
|
14
|
+
@app = app
|
15
|
+
@http = http
|
16
|
+
@notification = notification
|
17
|
+
@batch = batch
|
18
|
+
end
|
19
|
+
|
20
|
+
def perform
|
21
|
+
begin
|
22
|
+
handle_response(do_post)
|
23
|
+
rescue Rpush::DeliveryError => error
|
24
|
+
mark_failed(error.code, error.description)
|
25
|
+
raise
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def handle_response(response)
|
32
|
+
code = response.code.to_i
|
33
|
+
case code
|
34
|
+
when 200
|
35
|
+
ok(response)
|
36
|
+
when 406
|
37
|
+
not_acceptable(response)
|
38
|
+
when 412
|
39
|
+
precondition_failed(response)
|
40
|
+
when 503
|
41
|
+
service_unavailable(response)
|
42
|
+
else
|
43
|
+
handle_failure(code)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def handle_failure(code, msg=nil)
|
48
|
+
unless msg
|
49
|
+
msg = if FAILURE_MESSAGES.key?(code)
|
50
|
+
FAILURE_MESSAGES[code]
|
51
|
+
else
|
52
|
+
Rpush::Daemon::HTTP_STATUS_CODES[code]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
raise Rpush::DeliveryError.new(code, @notification.id, msg)
|
56
|
+
end
|
57
|
+
|
58
|
+
def ok(response)
|
59
|
+
status = status_from_response(response)
|
60
|
+
case status[:notification]
|
61
|
+
when ["Received"]
|
62
|
+
mark_delivered
|
63
|
+
log_info("#{@notification.id} sent successfully")
|
64
|
+
when ["QueueFull"]
|
65
|
+
mark_retryable(@notification, Time.now + (60*10))
|
66
|
+
log_warn("#{@notification.id} cannot be sent. The Queue is full.")
|
67
|
+
when ["Suppressed"]
|
68
|
+
handle_failure(200, "Notification was received but suppressed by the service.")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def not_acceptable(response)
|
73
|
+
retry_notification("Per-day throttling limit reached.")
|
74
|
+
end
|
75
|
+
|
76
|
+
def precondition_failed(response)
|
77
|
+
retry_notification("Device unreachable.")
|
78
|
+
end
|
79
|
+
|
80
|
+
def service_unavailable(response)
|
81
|
+
mark_retryable_exponential(@notification)
|
82
|
+
log_warn("Service Unavailable. " + retry_message)
|
83
|
+
end
|
84
|
+
|
85
|
+
def retry_message
|
86
|
+
"Notification #{@notification.id} will be retried after #{@notification.deliver_after.strftime("%Y-%m-%d %H:%M:%S")} (retry #{@notification.retries})."
|
87
|
+
end
|
88
|
+
|
89
|
+
def retry_notification(reason)
|
90
|
+
deliver_after = Time.now + (60*60)
|
91
|
+
mark_retryable(@notification, deliver_after)
|
92
|
+
log_warn("#{reason} " + retry_message)
|
93
|
+
end
|
94
|
+
|
95
|
+
def do_post
|
96
|
+
body = notification_to_xml
|
97
|
+
header = {
|
98
|
+
"Content-Length" => body.length.to_s,
|
99
|
+
"Content-Type" => "text/xml",
|
100
|
+
"X-WindowsPhone-Target" => "toast",
|
101
|
+
"X-NotificationClass" => '2'
|
102
|
+
}
|
103
|
+
post = Net::HTTP::Post.new(URI.parse(@notification.uri).path, initheader=header)
|
104
|
+
post.body = body
|
105
|
+
@http.request(URI.parse(@notification.uri), post)
|
106
|
+
end
|
107
|
+
|
108
|
+
def status_from_response(response)
|
109
|
+
headers = response.to_hash
|
110
|
+
{
|
111
|
+
notification: headers["x-notificationstatus"],
|
112
|
+
notification_channel: headers["x-subscriptionstatus"],
|
113
|
+
device_connection: headers["x-deviceconnectionstatus"]
|
114
|
+
}
|
115
|
+
end
|
116
|
+
|
117
|
+
def notification_to_xml
|
118
|
+
msg = @notification.alert.gsub(/&/, "&").gsub(/</, "<") \
|
119
|
+
.gsub(/>/, ">").gsub(/'/, "'").gsub(/"/, """)
|
120
|
+
<<-EOF
|
121
|
+
<?xml version="1.0" encoding="utf-8"?>
|
122
|
+
<wp:Notification xmlns:wp="WPNotification">
|
123
|
+
<wp:Toast>
|
124
|
+
<wp:Text1>#{msg}</wp:Text1>
|
125
|
+
</wp:Toast>
|
126
|
+
</wp:Notification>
|
127
|
+
EOF
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
data/lib/rpush/daemon.rb
ADDED
@@ -0,0 +1,140 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'socket'
|
3
|
+
require 'pathname'
|
4
|
+
require 'openssl'
|
5
|
+
|
6
|
+
require 'net/http/persistent'
|
7
|
+
|
8
|
+
require 'rpush/daemon/constants'
|
9
|
+
require 'rpush/daemon/reflectable'
|
10
|
+
require 'rpush/daemon/loggable'
|
11
|
+
require 'rpush/daemon/interruptible_sleep'
|
12
|
+
require 'rpush/daemon/delivery_error'
|
13
|
+
require 'rpush/daemon/retryable_error'
|
14
|
+
require 'rpush/daemon/too_many_requests_error'
|
15
|
+
require 'rpush/daemon/delivery'
|
16
|
+
require 'rpush/daemon/feeder'
|
17
|
+
require 'rpush/daemon/batch'
|
18
|
+
require 'rpush/daemon/app_runner'
|
19
|
+
require 'rpush/daemon/tcp_connection'
|
20
|
+
require 'rpush/daemon/dispatcher_loop'
|
21
|
+
require 'rpush/daemon/dispatcher_loop_collection'
|
22
|
+
require 'rpush/daemon/dispatcher/http'
|
23
|
+
require 'rpush/daemon/dispatcher/tcp'
|
24
|
+
require 'rpush/daemon/service_config_methods'
|
25
|
+
require 'rpush/daemon/retry_header_parser'
|
26
|
+
|
27
|
+
require 'rpush/daemon/apns/delivery'
|
28
|
+
require 'rpush/daemon/apns/disconnection_error'
|
29
|
+
require 'rpush/daemon/apns/certificate_expired_error'
|
30
|
+
require 'rpush/daemon/apns/feedback_receiver'
|
31
|
+
require 'rpush/daemon/apns'
|
32
|
+
|
33
|
+
require 'rpush/daemon/gcm/delivery'
|
34
|
+
require 'rpush/daemon/gcm'
|
35
|
+
|
36
|
+
require 'rpush/daemon/wpns/delivery'
|
37
|
+
require 'rpush/daemon/wpns'
|
38
|
+
|
39
|
+
require 'rpush/daemon/adm/delivery'
|
40
|
+
require 'rpush/daemon/adm'
|
41
|
+
|
42
|
+
module Rpush
|
43
|
+
module Daemon
|
44
|
+
class << self
|
45
|
+
attr_accessor :store
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.start
|
49
|
+
setup_signal_traps if trap_signals?
|
50
|
+
|
51
|
+
initialize_store
|
52
|
+
return unless store
|
53
|
+
|
54
|
+
if daemonize?
|
55
|
+
daemonize
|
56
|
+
store.after_daemonize
|
57
|
+
end
|
58
|
+
|
59
|
+
write_pid_file
|
60
|
+
AppRunner.sync
|
61
|
+
Feeder.start
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.shutdown(quiet = false)
|
65
|
+
puts "\nShutting down..." unless quiet
|
66
|
+
Feeder.stop
|
67
|
+
AppRunner.stop
|
68
|
+
delete_pid_file
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.initialize_store
|
72
|
+
return if store
|
73
|
+
begin
|
74
|
+
name = Rpush.config.store.to_s
|
75
|
+
require "rpush/daemon/store/#{name}"
|
76
|
+
self.store = Rpush::Daemon::Store.const_get(name.camelcase).new
|
77
|
+
rescue StandardError, LoadError => e
|
78
|
+
Rpush.logger.error("Failed to load '#{Rpush.config.store}' storage backend.")
|
79
|
+
Rpush.logger.error(e)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
protected
|
84
|
+
|
85
|
+
def self.daemonize?
|
86
|
+
!(Rpush.config.foreground || Rpush.config.embedded || Rpush.jruby?)
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.trap_signals?
|
90
|
+
!Rpush.config.embedded
|
91
|
+
end
|
92
|
+
|
93
|
+
def self.setup_signal_traps
|
94
|
+
@shutting_down = false
|
95
|
+
|
96
|
+
Signal.trap('SIGHUP') { AppRunner.sync }
|
97
|
+
Signal.trap('SIGUSR2') { AppRunner.debug }
|
98
|
+
|
99
|
+
['SIGINT', 'SIGTERM'].each do |signal|
|
100
|
+
Signal.trap(signal) { handle_shutdown_signal }
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.handle_shutdown_signal
|
105
|
+
exit 1 if @shutting_down
|
106
|
+
@shutting_down = true
|
107
|
+
shutdown
|
108
|
+
end
|
109
|
+
|
110
|
+
def self.write_pid_file
|
111
|
+
if !Rpush.config.pid_file.blank?
|
112
|
+
begin
|
113
|
+
File.open(Rpush.config.pid_file, 'w') { |f| f.puts Process.pid }
|
114
|
+
rescue SystemCallError => e
|
115
|
+
Rpush.logger.error("Failed to write PID to '#{Rpush.config.pid_file}': #{e.inspect}")
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.delete_pid_file
|
121
|
+
pid_file = Rpush.config.pid_file
|
122
|
+
File.delete(pid_file) if !pid_file.blank? && File.exists?(pid_file)
|
123
|
+
end
|
124
|
+
|
125
|
+
# :nocov:
|
126
|
+
def self.daemonize
|
127
|
+
if RUBY_VERSION < "1.9"
|
128
|
+
exit if fork
|
129
|
+
Process.setsid
|
130
|
+
exit if fork
|
131
|
+
Dir.chdir "/"
|
132
|
+
STDIN.reopen "/dev/null"
|
133
|
+
STDOUT.reopen "/dev/null", "a"
|
134
|
+
STDERR.reopen "/dev/null", "a"
|
135
|
+
else
|
136
|
+
Process.daemon
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Rpush
|
2
|
+
module Deprecatable
|
3
|
+
def self.included(base)
|
4
|
+
base.extend ClassMethods
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def deprecated(method_name, version, msg=nil)
|
9
|
+
instance_eval do
|
10
|
+
alias_method "#{method_name}_without_warning", method_name
|
11
|
+
end
|
12
|
+
warning = "#{method_name} is deprecated and will be removed from Rpush #{version}."
|
13
|
+
warning << " #{msg}" if msg
|
14
|
+
class_eval(<<-RUBY, __FILE__, __LINE__)
|
15
|
+
def #{method_name}(*args, &blk)
|
16
|
+
Rpush::Deprecation.warn(#{warning.inspect})
|
17
|
+
#{method_name}_without_warning(*args, &blk)
|
18
|
+
end
|
19
|
+
RUBY
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Rpush
|
2
|
+
class Deprecation
|
3
|
+
def self.muted
|
4
|
+
begin
|
5
|
+
orig_val = Thread.current[:rpush_mute_deprecations]
|
6
|
+
Thread.current[:rpush_mute_deprecations] = true
|
7
|
+
yield
|
8
|
+
ensure
|
9
|
+
Thread.current[:rpush_mute_deprecations] = orig_val
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.muted?
|
14
|
+
Thread.current[:rpush_mute_deprecations] == true
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.warn(msg)
|
18
|
+
unless Rpush::Deprecation.muted?
|
19
|
+
STDERR.puts "DEPRECATION WARNING: #{msg}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/rpush/embed.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
module Rpush
|
2
|
+
def self.embed(options = {})
|
3
|
+
Rpush.require_for_daemon
|
4
|
+
|
5
|
+
config = Rpush::ConfigurationWithoutDefaults.new
|
6
|
+
options.each { |k, v| config.send("#{k}=", v) }
|
7
|
+
config.embedded = true
|
8
|
+
Rpush.config.update(config)
|
9
|
+
Rpush::Daemon.start
|
10
|
+
|
11
|
+
Kernel.at_exit { shutdown }
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.shutdown
|
15
|
+
return unless Rpush.config.embedded
|
16
|
+
Rpush::Daemon.shutdown
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.sync
|
20
|
+
return unless Rpush.config.embedded
|
21
|
+
Rpush::Daemon::AppRunner.sync
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.debug
|
25
|
+
return unless Rpush.config.embedded
|
26
|
+
Rpush::Daemon::AppRunner.debug
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Rpush
|
2
|
+
module Gcm
|
3
|
+
class ExpiryCollapseKeyMutualInclusionValidator < ActiveModel::Validator
|
4
|
+
def validate(record)
|
5
|
+
if record.collapse_key && !record.expiry
|
6
|
+
record.errors[:expiry] << "must be set when using a collapse_key"
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Rpush
|
2
|
+
module Gcm
|
3
|
+
class Notification < Rpush::Notification
|
4
|
+
validates :registration_ids, :presence => true
|
5
|
+
|
6
|
+
validates_with Rpush::PayloadDataSizeValidator, limit: 4096
|
7
|
+
validates_with Rpush::RegistrationIdsCountValidator, limit: 1000
|
8
|
+
|
9
|
+
validates_with Rpush::Gcm::ExpiryCollapseKeyMutualInclusionValidator
|
10
|
+
|
11
|
+
def as_json
|
12
|
+
json = {
|
13
|
+
'registration_ids' => registration_ids,
|
14
|
+
'delay_while_idle' => delay_while_idle,
|
15
|
+
'data' => data
|
16
|
+
}
|
17
|
+
|
18
|
+
if collapse_key
|
19
|
+
json['collapse_key'] = collapse_key
|
20
|
+
end
|
21
|
+
|
22
|
+
if expiry
|
23
|
+
json['time_to_live'] = expiry
|
24
|
+
end
|
25
|
+
|
26
|
+
json
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/rpush/logger.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
module Rpush
|
2
|
+
class Logger
|
3
|
+
def initialize(options)
|
4
|
+
@options = options
|
5
|
+
|
6
|
+
begin
|
7
|
+
log_dir = File.join(Rails.root, 'log')
|
8
|
+
FileUtils.mkdir_p(log_dir)
|
9
|
+
log = File.open(File.join(log_dir, 'rpush.log'), 'a')
|
10
|
+
log.sync = true
|
11
|
+
setup_logger(log)
|
12
|
+
rescue Errno::ENOENT, Errno::EPERM => e
|
13
|
+
@logger = nil
|
14
|
+
error(e)
|
15
|
+
error('Logging disabled.')
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def info(msg)
|
20
|
+
log(:info, msg)
|
21
|
+
end
|
22
|
+
|
23
|
+
def error(msg)
|
24
|
+
log(:error, msg, 'ERROR', STDERR)
|
25
|
+
end
|
26
|
+
|
27
|
+
def warn(msg)
|
28
|
+
log(:warn, msg, 'WARNING', STDERR)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def setup_logger(log)
|
34
|
+
if Rpush.config.logger
|
35
|
+
@logger = Rpush.config.logger
|
36
|
+
elsif ActiveSupport.const_defined?('BufferedLogger')
|
37
|
+
@logger = ActiveSupport::BufferedLogger.new(log, Rails.logger.level)
|
38
|
+
@logger.auto_flushing = Rails.logger.respond_to?(:auto_flushing) ? Rails.logger.auto_flushing : true
|
39
|
+
else
|
40
|
+
@logger = ActiveSupport::Logger.new(log, Rails.logger.level)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def log(where, msg, prefix = nil, io = STDOUT)
|
45
|
+
if msg.is_a?(Exception)
|
46
|
+
formatted_backtrace = msg.backtrace.join("\n")
|
47
|
+
msg = "#{msg.class.name}, #{msg.message}\n#{formatted_backtrace}"
|
48
|
+
end
|
49
|
+
|
50
|
+
formatted_msg = "[#{Time.now.to_s(:db)}] "
|
51
|
+
formatted_msg << "[#{prefix}] " if prefix
|
52
|
+
formatted_msg << msg
|
53
|
+
|
54
|
+
if io == STDERR
|
55
|
+
io.puts formatted_msg
|
56
|
+
elsif @options[:foreground]
|
57
|
+
io.puts formatted_msg
|
58
|
+
end
|
59
|
+
|
60
|
+
@logger.send(where, formatted_msg) if @logger
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Rpush
|
2
|
+
module MultiJsonHelper
|
3
|
+
def multi_json_load(string, options = {})
|
4
|
+
# Calling load on multi_json less than v1.3.0 attempts to load a file from disk.
|
5
|
+
if Gem.loaded_specs['multi_json'].version >= Gem::Version.create('1.3.0')
|
6
|
+
MultiJson.load(string, options)
|
7
|
+
else
|
8
|
+
MultiJson.decode(string, options)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def multi_json_dump(string, options = {})
|
13
|
+
MultiJson.respond_to?(:dump) ? MultiJson.dump(string, options) : MultiJson.encode(string, options)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|