apple_shove 1.0.2 → 1.1.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 +17 -0
- data/lib/apple_shove/apns/connection.rb +13 -10
- data/lib/apple_shove/apns/feedback_connection.rb +2 -4
- data/lib/apple_shove/apns/notify_connection.rb +16 -15
- data/lib/apple_shove/apple_shove.rb +10 -5
- data/lib/apple_shove/demultiplexer.rb +4 -5
- data/lib/apple_shove/logger.rb +30 -6
- data/lib/apple_shove/notification.rb +2 -2
- data/lib/apple_shove/openssl_helper.rb +15 -0
- data/lib/apple_shove/version.rb +1 -1
- data/spec/apple_shove_spec.rb +10 -0
- data/spec/notification_helper.rb +2 -2
- metadata +7 -4
data/README.md
CHANGED
@@ -37,6 +37,21 @@ Willing to give it a try? Onward...
|
|
37
37
|
|
38
38
|
## Usage
|
39
39
|
|
40
|
+
### Quick Note on Certificate/Key Handling
|
41
|
+
|
42
|
+
AppleShove expects certificates and keys to be bundled together in PKCS#12 PEM format. If you want to test the validity of your PKCS#12 file, you can do something similar to this:
|
43
|
+
|
44
|
+
apns_p12 = File.read('my_cert.p12')
|
45
|
+
begin
|
46
|
+
AppleShove.try_p12(apns_p12)
|
47
|
+
rescue Exception => e
|
48
|
+
puts "the PKCS#12 file is invalid: #{e.message}"
|
49
|
+
else
|
50
|
+
puts "it's valid!"
|
51
|
+
end
|
52
|
+
|
53
|
+
If you send a notification with an invalid PKCS#12 file, the notification will fail downstream.
|
54
|
+
|
40
55
|
### Sending Notifications
|
41
56
|
|
42
57
|
Sending a notification request looks like this:
|
@@ -81,6 +96,8 @@ We also have a feedback mechanism in place:
|
|
81
96
|
|
82
97
|
## Installation
|
83
98
|
|
99
|
+
If you haven't already, install redis. It's normally available via brew, apt-get, and yum, but you can also build from [source](http://redis.io/download).
|
100
|
+
|
84
101
|
Add this line to your application's Gemfile:
|
85
102
|
|
86
103
|
gem 'apple_shove'
|
@@ -6,10 +6,12 @@ module AppleShove
|
|
6
6
|
|
7
7
|
attr_reader :last_used
|
8
8
|
|
9
|
-
def initialize(
|
10
|
-
@host
|
11
|
-
@port
|
12
|
-
@
|
9
|
+
def initialize(host, port, p12_string)
|
10
|
+
@host = host
|
11
|
+
@port = port
|
12
|
+
@p12_string = p12_string
|
13
|
+
|
14
|
+
@p12 = nil
|
13
15
|
end
|
14
16
|
|
15
17
|
# lazy connect the socket
|
@@ -31,12 +33,13 @@ module AppleShove
|
|
31
33
|
private
|
32
34
|
|
33
35
|
def connect
|
34
|
-
@
|
35
|
-
@sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true)
|
36
|
-
|
36
|
+
@p12 ||= OpenSSLHelper.pkcs12_from_pem(@p12_string)
|
37
37
|
context = ::OpenSSL::SSL::SSLContext.new
|
38
|
-
context.cert =
|
39
|
-
context.key =
|
38
|
+
context.cert = @p12.certificate
|
39
|
+
context.key = @p12.key
|
40
|
+
|
41
|
+
@sock = TCPSocket.new(@host, @port)
|
42
|
+
@sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true)
|
40
43
|
@ssl_sock = ::OpenSSL::SSL::SSLSocket.new(@sock, context)
|
41
44
|
@ssl_sock.sync = true
|
42
45
|
|
@@ -49,4 +52,4 @@ module AppleShove
|
|
49
52
|
|
50
53
|
end
|
51
54
|
end
|
52
|
-
end
|
55
|
+
end
|
@@ -2,12 +2,10 @@ module AppleShove
|
|
2
2
|
module APNS
|
3
3
|
class FeedbackConnection < Connection
|
4
4
|
|
5
|
-
def initialize(
|
5
|
+
def initialize(p12, sandbox)
|
6
6
|
host = "feedback.#{opts[:sandbox] ? 'sandbox.' : ''}push.apple.com"
|
7
7
|
|
8
|
-
super
|
9
|
-
host: host,
|
10
|
-
port: 2196
|
8
|
+
super host, 2196, p12
|
11
9
|
end
|
12
10
|
|
13
11
|
def device_tokens
|
@@ -8,20 +8,18 @@ module AppleShove
|
|
8
8
|
attr_accessor :pending_notifications
|
9
9
|
attr_reader :name
|
10
10
|
|
11
|
-
def initialize(
|
12
|
-
@name = self.class.generate_name(
|
11
|
+
def initialize(p12, sandbox)
|
12
|
+
@name = self.class.generate_name(p12, sandbox)
|
13
13
|
@last_message = nil
|
14
14
|
@pending_notifications = 0
|
15
15
|
|
16
|
-
host = "gateway.#{
|
16
|
+
host = "gateway.#{sandbox ? 'sandbox.' : ''}push.apple.com"
|
17
17
|
|
18
|
-
super
|
19
|
-
host: host,
|
20
|
-
port: 2195
|
18
|
+
super host, 2195, p12
|
21
19
|
end
|
22
20
|
|
23
|
-
def self.generate_name(
|
24
|
-
Digest::SHA1.hexdigest("#{
|
21
|
+
def self.generate_name(p12, sandbox)
|
22
|
+
Digest::SHA1.hexdigest("#{p12}#{sandbox}")
|
25
23
|
end
|
26
24
|
|
27
25
|
exclusive
|
@@ -35,34 +33,37 @@ module AppleShove
|
|
35
33
|
message = notification.binary_message
|
36
34
|
|
37
35
|
if @last_used && Time.now - @last_used > CONFIG[:reconnect_timer] * 60
|
38
|
-
Logger.info("
|
36
|
+
Logger.info("refreshing connection", self, notification)
|
39
37
|
reconnect
|
40
38
|
end
|
41
39
|
|
42
40
|
begin
|
43
41
|
socket.write message
|
44
42
|
rescue Errno::EPIPE
|
45
|
-
Logger.warn("
|
43
|
+
Logger.warn("broken pipe. reconnecting.", self, notification)
|
46
44
|
reconnect
|
47
45
|
# EPIPE raises on the second write to a closed pipe. We need to resend
|
48
46
|
# the previous notification that didn't make it through.
|
49
47
|
socket.write @last_message if @last_message
|
50
48
|
retry
|
51
49
|
rescue Errno::ETIMEDOUT
|
52
|
-
Logger.warn("
|
50
|
+
Logger.warn("timeout. reconnecting.", self, notification)
|
53
51
|
reconnect
|
54
52
|
retry
|
53
|
+
rescue Exception => e
|
54
|
+
Logger.error("error sending notification: #{e.message}", self, notification)
|
55
|
+
else
|
56
|
+
Logger.info("delivered notification", self, notification)
|
55
57
|
end
|
56
|
-
|
58
|
+
|
57
59
|
@last_message = message
|
58
60
|
@last_used = Time.now
|
59
61
|
@pending_notifications -= 1
|
60
|
-
Logger.info("#{@name}\tdelivered notification")
|
61
62
|
end
|
62
63
|
|
63
64
|
def shutdown
|
64
65
|
while @pending_notifications > 0
|
65
|
-
Logger.info("
|
66
|
+
Logger.info("waiting to shut down. #{@pending_notifications} job(s) remaining.", self)
|
66
67
|
sleep 1
|
67
68
|
end
|
68
69
|
|
@@ -72,4 +73,4 @@ module AppleShove
|
|
72
73
|
|
73
74
|
end
|
74
75
|
end
|
75
|
-
end
|
76
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module AppleShove
|
2
2
|
|
3
|
-
def self.notify(
|
4
|
-
notification = Notification.new
|
3
|
+
def self.notify(p12, device_token, payload, sandbox = false)
|
4
|
+
notification = Notification.new p12: p12,
|
5
5
|
device_token: device_token,
|
6
6
|
payload: payload,
|
7
7
|
sandbox: sandbox
|
@@ -12,9 +12,8 @@ module AppleShove
|
|
12
12
|
true
|
13
13
|
end
|
14
14
|
|
15
|
-
def self.feedback_tokens(
|
16
|
-
conn = APNS::FeedbackConnection.new
|
17
|
-
sandbox: sandbox
|
15
|
+
def self.feedback_tokens(p12, sandbox = false)
|
16
|
+
conn = APNS::FeedbackConnection.new p12, sandbox
|
18
17
|
|
19
18
|
conn.device_tokens
|
20
19
|
end
|
@@ -30,4 +29,10 @@ module AppleShove
|
|
30
29
|
"queue size:\t#{size}"
|
31
30
|
end
|
32
31
|
|
32
|
+
# raises an exception if the p12 string is invalid
|
33
|
+
def self.try_p12(p12)
|
34
|
+
OpenSSLHelper.pkcs12_from_pem(p12)
|
35
|
+
true
|
36
|
+
end
|
37
|
+
|
33
38
|
end
|
@@ -31,16 +31,15 @@ module AppleShove
|
|
31
31
|
private
|
32
32
|
|
33
33
|
def get_connection(notification)
|
34
|
-
key = APNS::NotifyConnection.generate_name(notification.
|
34
|
+
key = APNS::NotifyConnection.generate_name(notification.p12, notification.sandbox)
|
35
35
|
connection = @connections[key]
|
36
36
|
|
37
37
|
unless connection
|
38
38
|
retire_oldest_connection if @connections.count >= @max_connections
|
39
39
|
|
40
|
-
connection = APNS::NotifyConnection.new
|
41
|
-
sandbox: notification.sandbox
|
40
|
+
connection = APNS::NotifyConnection.new notification.p12, notification.sandbox
|
42
41
|
@connections[key] = connection
|
43
|
-
Logger.info "
|
42
|
+
Logger.info "created connection to APNS (#{@connections.count} total)", connection
|
44
43
|
end
|
45
44
|
|
46
45
|
connection
|
@@ -53,7 +52,7 @@ module AppleShove
|
|
53
52
|
@connections.delete key
|
54
53
|
conn.shutdown
|
55
54
|
|
56
|
-
Logger.info "
|
55
|
+
Logger.info "destroyed connection to APNS (#{@connections.count} total)", connection
|
57
56
|
end
|
58
57
|
end
|
59
58
|
|
data/lib/apple_shove/logger.rb
CHANGED
@@ -19,11 +19,35 @@ module AppleShove
|
|
19
19
|
self
|
20
20
|
end
|
21
21
|
|
22
|
-
def self.error(msg
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
def self.
|
27
|
-
|
22
|
+
def self.error(msg, connection = nil, notification = nil)
|
23
|
+
log('error', msg, connection, notification)
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.debug(msg, connection = nil, notification = nil)
|
27
|
+
log('debug', msg, connection, notification)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.fatal(msg, connection = nil, notification = nil)
|
31
|
+
log('fatal', msg, connection, notification)
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.info(msg, connection = nil, notification = nil)
|
35
|
+
log('info', msg, connection, notification)
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.warn(msg, connection = nil, notification = nil)
|
39
|
+
log('warn', msg, connection, notification)
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def self.log(severity, msg, connection, notification)
|
45
|
+
output = ''
|
46
|
+
output += "#{connection.name}\t" if connection && connection.respond_to?("name")
|
47
|
+
output += "#{notification.device_token}\t" if notification
|
48
|
+
output += msg
|
49
|
+
|
50
|
+
instance.send(severity, output)
|
51
|
+
end
|
28
52
|
end
|
29
53
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module AppleShove
|
2
2
|
class Notification
|
3
3
|
|
4
|
-
attr_accessor :
|
4
|
+
attr_accessor :p12, :sandbox, :device_token, :payload
|
5
5
|
|
6
6
|
def initialize(attributes = {})
|
7
7
|
attributes.each { |k, v| self.send("#{k}=", v) }
|
@@ -31,4 +31,4 @@ module AppleShove
|
|
31
31
|
end
|
32
32
|
|
33
33
|
end
|
34
|
-
end
|
34
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
module AppleShove
|
4
|
+
class OpenSSLHelper
|
5
|
+
|
6
|
+
def self.pkcs12_from_pem(p12_pem)
|
7
|
+
key = ::OpenSSL::PKey::RSA.new p12_pem
|
8
|
+
cert = ::OpenSSL::X509::Certificate.new p12_pem
|
9
|
+
p12 = ::OpenSSL::PKCS12.create nil, nil, key, cert
|
10
|
+
|
11
|
+
p12
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
data/lib/apple_shove/version.rb
CHANGED
data/spec/notification_helper.rb
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
module NotificationHelper
|
2
2
|
|
3
3
|
def generate_notification
|
4
|
-
|
4
|
+
p12 = "DummyP12"
|
5
5
|
sandbox = false
|
6
6
|
device_token = hex(64)
|
7
7
|
payload = { mdm: "#{hex(8)}-#{hex(4)}-#{hex(4)}-#{hex(4)}-#{hex(12)}".downcase }
|
8
8
|
|
9
|
-
AppleShove::Notification.new
|
9
|
+
AppleShove::Notification.new p12: p12,
|
10
10
|
sandbox: sandbox,
|
11
11
|
device_token: device_token,
|
12
12
|
payload: payload
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: apple_shove
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-05-
|
12
|
+
date: 2013-05-20 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -130,10 +130,12 @@ files:
|
|
130
130
|
- lib/apple_shove/logger.rb
|
131
131
|
- lib/apple_shove/notification.rb
|
132
132
|
- lib/apple_shove/notification_queue.rb
|
133
|
+
- lib/apple_shove/openssl_helper.rb
|
133
134
|
- lib/apple_shove/tasks.rb
|
134
135
|
- lib/apple_shove/version.rb
|
135
136
|
- lib/tasks/apple_shove.rake
|
136
137
|
- script/daemon
|
138
|
+
- spec/apple_shove_spec.rb
|
137
139
|
- spec/demultiplexer_spec.rb
|
138
140
|
- spec/notification_helper.rb
|
139
141
|
- spec/notification_queue_spec.rb
|
@@ -153,7 +155,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
153
155
|
version: '0'
|
154
156
|
segments:
|
155
157
|
- 0
|
156
|
-
hash:
|
158
|
+
hash: 2826984217209674541
|
157
159
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
158
160
|
none: false
|
159
161
|
requirements:
|
@@ -162,7 +164,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
162
164
|
version: '0'
|
163
165
|
segments:
|
164
166
|
- 0
|
165
|
-
hash:
|
167
|
+
hash: 2826984217209674541
|
166
168
|
requirements: []
|
167
169
|
rubyforge_project:
|
168
170
|
rubygems_version: 1.8.24
|
@@ -170,6 +172,7 @@ signing_key:
|
|
170
172
|
specification_version: 3
|
171
173
|
summary: ''
|
172
174
|
test_files:
|
175
|
+
- spec/apple_shove_spec.rb
|
173
176
|
- spec/demultiplexer_spec.rb
|
174
177
|
- spec/notification_helper.rb
|
175
178
|
- spec/notification_queue_spec.rb
|