pushr-apns 1.0.0.pre.1
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/MIT-LICENSE +20 -0
- data/README.md +7 -0
- data/lib/pushr/apns.rb +14 -0
- data/lib/pushr/configuration_apns.rb +21 -0
- data/lib/pushr/daemon/apns.rb +30 -0
- data/lib/pushr/daemon/apns_support/connection_apns.rb +154 -0
- data/lib/pushr/daemon/apns_support/disconnection_error.rb +18 -0
- data/lib/pushr/daemon/apns_support/feedback_receiver.rb +61 -0
- data/lib/pushr/daemon/apns_support/interruptible_sleep.rb +48 -0
- data/lib/pushr/feedback_apns.rb +12 -0
- data/lib/pushr/message_apns.rb +65 -0
- data/lib/pushr-apns/version.rb +3 -0
- data/spec/lib/pushr/apns_spec.rb +7 -0
- data/spec/lib/pushr/apns_support/connection_apns_spec.rb +47 -0
- data/spec/lib/pushr/apns_support/disconnection_error_spec.rb +21 -0
- data/spec/lib/pushr/apns_support/feedback_receiver_spec.rb +9 -0
- data/spec/lib/pushr/apns_support/interruptible_sleep_spec.rb +38 -0
- data/spec/lib/pushr/configuration_apns_spec.rb +37 -0
- data/spec/lib/pushr/feedback_apns_spec.rb +27 -0
- data/spec/lib/pushr/message_apns_spec.rb +38 -0
- data/spec/spec_helper.rb +30 -0
- data/spec/support/cert_with_password.pem +90 -0
- data/spec/support/cert_without_password.pem +59 -0
- metadata +231 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: cc63a9d097c133a4caf7e2fe309d18c1d344440f
|
4
|
+
data.tar.gz: 4da07aeccda83b5c40ee1c0833ad54a25ba6af74
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9d21d622874f347b12241dc570c4f48506cd34570e4d8ccf68ee41e413f5a739fc865a07f47a7be1e389906a9f2112de3910078641aca96a212f3380f9980f83
|
7
|
+
data.tar.gz: 743a1c257e31d7ec9236ce437f77bed12a17a87a6800728c0590032119298f125cd09c9d91554c53630c1f6a5073437a9d6b0c65ef37fab5f4386e3a6b465bd3
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2012 YOURNAME
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
# PushrApns
|
2
|
+
|
3
|
+
[](https://travis-ci.org/9to5/pushr-apns)
|
4
|
+
[](https://codeclimate.com/github/9to5/pushr-apns)
|
5
|
+
[](https://coveralls.io/r/9to5/pushr-apns)
|
6
|
+
|
7
|
+
Please see [pushr-core](https://github.com/tompesman/pushr-core) for more information.
|
data/lib/pushr/apns.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'pathname'
|
3
|
+
require 'pushr-apns/version'
|
4
|
+
require 'pushr/configuration'
|
5
|
+
require 'pushr/configuration_apns'
|
6
|
+
require 'pushr/message'
|
7
|
+
require 'pushr/message_apns'
|
8
|
+
require 'pushr/feedback'
|
9
|
+
require 'pushr/feedback_apns'
|
10
|
+
require 'pushr/daemon/apns'
|
11
|
+
require 'pushr/daemon/apns_support/interruptible_sleep'
|
12
|
+
require 'pushr/daemon/apns_support/disconnection_error'
|
13
|
+
require 'pushr/daemon/apns_support/connection_apns'
|
14
|
+
require 'pushr/daemon/apns_support/feedback_receiver'
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Pushr
|
2
|
+
class ConfigurationApns < Pushr::Configuration
|
3
|
+
attr_accessor :id, :type, :app, :enabled, :connections, :certificate, :certificate_password,
|
4
|
+
:sandbox, :feedback_poll, :skip_check_for_error
|
5
|
+
validates :certificate, presence: true
|
6
|
+
validates :sandbox, inclusion: { in: [true, false] }
|
7
|
+
validates :feedback_poll, presence: true
|
8
|
+
validates :skip_check_for_error, inclusion: { in: [true, false] }, allow_blank: true
|
9
|
+
|
10
|
+
def name
|
11
|
+
:apns
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_json
|
15
|
+
hsh = { type: self.class.to_s, app: app, enabled: enabled, connections: connections, certificate: certificate,
|
16
|
+
certificate_password: certificate_password, sandbox: sandbox, feedback_poll: feedback_poll,
|
17
|
+
skip_check_for_error: skip_check_for_error }
|
18
|
+
MultiJson.dump(hsh)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Pushr
|
2
|
+
module Daemon
|
3
|
+
class Apns
|
4
|
+
attr_accessor :configuration
|
5
|
+
|
6
|
+
def initialize(options)
|
7
|
+
self.configuration = options
|
8
|
+
|
9
|
+
@feedback_receiver = ApnsSupport::FeedbackReceiver.new(configuration)
|
10
|
+
start_feedback
|
11
|
+
end
|
12
|
+
|
13
|
+
def connectiontype
|
14
|
+
ApnsSupport::ConnectionApns
|
15
|
+
end
|
16
|
+
|
17
|
+
def start_feedback
|
18
|
+
@feedback_receiver.start
|
19
|
+
end
|
20
|
+
|
21
|
+
def stop_feedback
|
22
|
+
@feedback_receiver.stop
|
23
|
+
end
|
24
|
+
|
25
|
+
def stop
|
26
|
+
stop_feedback
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
module Pushr
|
2
|
+
module Daemon
|
3
|
+
module ApnsSupport
|
4
|
+
class ConnectionError < StandardError; end
|
5
|
+
|
6
|
+
class ConnectionApns
|
7
|
+
attr_reader :name, :configuration
|
8
|
+
attr_accessor :last_write
|
9
|
+
IDLE_PERIOD = 30 * 60
|
10
|
+
SELECT_TIMEOUT = 0.2
|
11
|
+
ERROR_TUPLE_BYTES = 6
|
12
|
+
APN_ERRORS = {
|
13
|
+
1 => 'Processing error',
|
14
|
+
2 => 'Missing device token',
|
15
|
+
3 => 'Missing topic',
|
16
|
+
4 => 'Missing payload',
|
17
|
+
5 => 'Missing token size',
|
18
|
+
6 => 'Missing topic size',
|
19
|
+
7 => 'Missing payload size',
|
20
|
+
8 => 'Invalid token',
|
21
|
+
255 => 'None (unknown error)'
|
22
|
+
}
|
23
|
+
|
24
|
+
def initialize(configuration, i = nil)
|
25
|
+
@configuration = configuration
|
26
|
+
if i
|
27
|
+
# Apns push connection
|
28
|
+
@name = "#{@configuration.app}: ConnectionApns #{i}"
|
29
|
+
@host = "gateway.#{configuration.sandbox ? 'sandbox.' : ''}push.apple.com"
|
30
|
+
@port = 2195
|
31
|
+
else
|
32
|
+
@name = "#{@configuration.app}: FeedbackReceiver"
|
33
|
+
@host = "feedback.#{configuration.sandbox ? 'sandbox.' : ''}push.apple.com"
|
34
|
+
@port = 2196
|
35
|
+
end
|
36
|
+
written
|
37
|
+
end
|
38
|
+
|
39
|
+
def connect
|
40
|
+
@ssl_context = setup_ssl_context
|
41
|
+
@tcp_socket, @ssl_socket = connect_socket
|
42
|
+
end
|
43
|
+
|
44
|
+
def close
|
45
|
+
@ssl_socket.close if @ssl_socket
|
46
|
+
@tcp_socket.close if @tcp_socket
|
47
|
+
rescue IOError
|
48
|
+
end
|
49
|
+
|
50
|
+
def read(num_bytes)
|
51
|
+
@ssl_socket.read(num_bytes)
|
52
|
+
end
|
53
|
+
|
54
|
+
def select(timeout)
|
55
|
+
IO.select([@ssl_socket], nil, nil, timeout)
|
56
|
+
end
|
57
|
+
|
58
|
+
def write(data)
|
59
|
+
reconnect_idle if idle_period_exceeded?
|
60
|
+
|
61
|
+
retry_count = 0
|
62
|
+
|
63
|
+
begin
|
64
|
+
write_data(data)
|
65
|
+
rescue Errno::EPIPE, Errno::ETIMEDOUT, Errno::ECONNRESET, OpenSSL::SSL::SSLError => e
|
66
|
+
retry_count += 1
|
67
|
+
|
68
|
+
if retry_count == 1
|
69
|
+
Pushr::Daemon.logger.error("[#{@name}] Lost connection to #{@host}:#{@port} (#{e.class.name}), reconnecting...")
|
70
|
+
end
|
71
|
+
|
72
|
+
if retry_count <= 3
|
73
|
+
reconnect
|
74
|
+
sleep 1
|
75
|
+
retry
|
76
|
+
else
|
77
|
+
raise ConnectionError, "#{@name} tried #{retry_count - 1} times to reconnect but failed (#{e.class.name})."
|
78
|
+
end
|
79
|
+
end
|
80
|
+
check_for_error(data)
|
81
|
+
end
|
82
|
+
|
83
|
+
def reconnect
|
84
|
+
close
|
85
|
+
@tcp_socket, @ssl_socket = connect_socket
|
86
|
+
end
|
87
|
+
|
88
|
+
def check_for_error(notification)
|
89
|
+
# check for true, because check_for_error can be nil
|
90
|
+
return if @configuration.skip_check_for_error == true
|
91
|
+
|
92
|
+
if select(SELECT_TIMEOUT)
|
93
|
+
error = nil
|
94
|
+
|
95
|
+
if tuple = read(ERROR_TUPLE_BYTES)
|
96
|
+
cmd, code, notification_id = tuple.unpack('ccN')
|
97
|
+
|
98
|
+
description = APN_ERRORS[code.to_i] || 'Unknown error. Possible push bug?'
|
99
|
+
error = Pushr::Daemon::DeliveryError.new(code, notification_id, description, 'APNS')
|
100
|
+
else
|
101
|
+
error = DisconnectionError.new
|
102
|
+
end
|
103
|
+
|
104
|
+
begin
|
105
|
+
Pushr::Daemon.logger.error("[#{@name}] Error received, reconnecting...")
|
106
|
+
reconnect
|
107
|
+
ensure
|
108
|
+
fail error if error
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
protected
|
114
|
+
|
115
|
+
def reconnect_idle
|
116
|
+
Pushr::Daemon.logger.info("[#{@name}] Idle period exceeded, reconnecting...")
|
117
|
+
reconnect
|
118
|
+
end
|
119
|
+
|
120
|
+
def idle_period_exceeded?
|
121
|
+
Time.now - last_write > IDLE_PERIOD
|
122
|
+
end
|
123
|
+
|
124
|
+
def write_data(data)
|
125
|
+
@ssl_socket.write(data.to_message)
|
126
|
+
@ssl_socket.flush
|
127
|
+
written
|
128
|
+
end
|
129
|
+
|
130
|
+
def written
|
131
|
+
self.last_write = Time.now
|
132
|
+
end
|
133
|
+
|
134
|
+
def setup_ssl_context
|
135
|
+
ssl_context = OpenSSL::SSL::SSLContext.new
|
136
|
+
ssl_context.key = OpenSSL::PKey::RSA.new(configuration.certificate, configuration.certificate_password)
|
137
|
+
ssl_context.cert = OpenSSL::X509::Certificate.new(configuration.certificate)
|
138
|
+
ssl_context
|
139
|
+
end
|
140
|
+
|
141
|
+
def connect_socket
|
142
|
+
tcp_socket = TCPSocket.new(@host, @port)
|
143
|
+
tcp_socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, 1)
|
144
|
+
tcp_socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
145
|
+
ssl_socket = OpenSSL::SSL::SSLSocket.new(tcp_socket, @ssl_context)
|
146
|
+
ssl_socket.sync = true
|
147
|
+
ssl_socket.connect
|
148
|
+
Pushr::Daemon.logger.info("[#{@name}] Connected to #{@host}:#{@port}")
|
149
|
+
[tcp_socket, ssl_socket]
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Pushr
|
2
|
+
module Daemon
|
3
|
+
module ApnsSupport
|
4
|
+
class DisconnectionError < StandardError
|
5
|
+
attr_reader :code, :description
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@code = nil
|
9
|
+
@description = 'APNs disconnected without returning an error.'
|
10
|
+
end
|
11
|
+
|
12
|
+
def message
|
13
|
+
'The APNs disconnected without returning an error. This may indicate you are using an invalid certificate for the host.'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Pushr
|
2
|
+
module Daemon
|
3
|
+
module ApnsSupport
|
4
|
+
class FeedbackReceiver
|
5
|
+
|
6
|
+
FEEDBACK_TUPLE_BYTES = 38
|
7
|
+
|
8
|
+
def initialize(configuration)
|
9
|
+
@configuration = configuration
|
10
|
+
@interruptible_sleep = InterruptibleSleep.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def start
|
14
|
+
@thread = Thread.new do
|
15
|
+
loop do
|
16
|
+
break if @stop
|
17
|
+
check_for_feedback
|
18
|
+
@interruptible_sleep.sleep @configuration.feedback_poll
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def stop
|
24
|
+
@stop = true
|
25
|
+
@interruptible_sleep.interrupt_sleep
|
26
|
+
@thread.join if @thread
|
27
|
+
end
|
28
|
+
|
29
|
+
def check_for_feedback
|
30
|
+
connection = nil
|
31
|
+
begin
|
32
|
+
connection = ConnectionApns.new(@configuration)
|
33
|
+
connection.connect
|
34
|
+
|
35
|
+
while tuple = connection.read(FEEDBACK_TUPLE_BYTES)
|
36
|
+
timestamp, device = parse_tuple(tuple)
|
37
|
+
create_feedback(connection, timestamp, device)
|
38
|
+
end
|
39
|
+
rescue StandardError => e
|
40
|
+
Pushr::Daemon.logger.error(e)
|
41
|
+
ensure
|
42
|
+
connection.close if connection
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
protected
|
47
|
+
|
48
|
+
def parse_tuple(tuple)
|
49
|
+
failed_at, _, device = tuple.unpack('N1n1H*')
|
50
|
+
[Time.at(failed_at).utc, device]
|
51
|
+
end
|
52
|
+
|
53
|
+
def create_feedback(connection, failed_at, device)
|
54
|
+
formatted_failed_at = failed_at.strftime('%Y-%m-%d %H:%M:%S UTC')
|
55
|
+
Pushr::Daemon.logger.info("[#{connection.name}: Delivery failed at #{formatted_failed_at} for #{device}")
|
56
|
+
Pushr::FeedbackApns.new(app: @configuration.app, failed_at: failed_at, device: device, follow_up: 'delete').save
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Pushr
|
2
|
+
module Daemon
|
3
|
+
module ApnsSupport
|
4
|
+
class InterruptibleSleep
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@sleep_reader, @wake_writer = IO.pipe
|
8
|
+
end
|
9
|
+
|
10
|
+
# wait for the given timeout in seconds, or data was written to the pipe.
|
11
|
+
# @return [boolean] true if the sleep was interrupted, or false
|
12
|
+
def sleep(timeout)
|
13
|
+
read_ports = [@sleep_reader]
|
14
|
+
rs, = IO.select(read_ports, nil, nil, timeout) rescue nil
|
15
|
+
|
16
|
+
# consume all data on the readable io's so that our next call will wait for more data
|
17
|
+
perform_io(rs, @sleep_reader, :read_nonblock)
|
18
|
+
|
19
|
+
!rs.nil? && rs.any?
|
20
|
+
end
|
21
|
+
|
22
|
+
# writing to the pipe will wake the sleeping thread
|
23
|
+
def interrupt_sleep
|
24
|
+
@wake_writer.write('.')
|
25
|
+
end
|
26
|
+
|
27
|
+
def close
|
28
|
+
@sleep_reader.close rescue nil
|
29
|
+
@wake_writer.close rescue nil
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def perform_io(selected, io, meth)
|
35
|
+
if selected && selected.include?(io)
|
36
|
+
while true
|
37
|
+
begin
|
38
|
+
io.__send__(meth, 1)
|
39
|
+
rescue Errno::EAGAIN, IO::WaitReadable
|
40
|
+
break
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Pushr
|
2
|
+
class FeedbackApns < Pushr::Feedback
|
3
|
+
attr_accessor :type, :app, :device, :follow_up, :failed_at
|
4
|
+
|
5
|
+
validates :device, format: { with: /\A[a-z0-9]{64}\z/ }
|
6
|
+
validates :follow_up, inclusion: { in: %w(delete), message: '%{value} is not a valid follow-up' }
|
7
|
+
|
8
|
+
def to_json
|
9
|
+
MultiJson.dump(type: 'Pushr::FeedbackApns', app: app, device: device, follow_up: follow_up, failed_at: failed_at)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Pushr
|
2
|
+
class MessageApns < Pushr::Message
|
3
|
+
POSTFIX = 'apns'
|
4
|
+
|
5
|
+
attr_accessor :type, :app, :device, :alert, :badge, :sound, :expiry, :attributes_for_device
|
6
|
+
|
7
|
+
validates :badge, numericality: true, allow_nil: true
|
8
|
+
validates :expiry, numericality: true, presence: true
|
9
|
+
validates :device, format: { with: /\A[a-z0-9]{64}\z/ }
|
10
|
+
validate :max_payload_size
|
11
|
+
|
12
|
+
def alert=(alert)
|
13
|
+
if alert.is_a?(Hash)
|
14
|
+
@alert = MultiJson.dump(alert)
|
15
|
+
else
|
16
|
+
@alert = alert
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def alert
|
21
|
+
string_or_json = @alert
|
22
|
+
return MultiJson.load(string_or_json)
|
23
|
+
rescue
|
24
|
+
return string_or_json
|
25
|
+
end
|
26
|
+
|
27
|
+
# This method conforms to the enhanced binary format.
|
28
|
+
# http://developer.apple.com/library/ios/#documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingWIthAPS/CommunicatingWIthAPS.html#//apple_ref/doc/uid/TP40008194-CH101-SW4
|
29
|
+
def to_message
|
30
|
+
[1, 0, expiry, 0, 32, device, payload_size, payload].pack('cNNccH*na*')
|
31
|
+
end
|
32
|
+
|
33
|
+
def payload
|
34
|
+
MultiJson.dump(as_json)
|
35
|
+
end
|
36
|
+
|
37
|
+
def payload_size
|
38
|
+
payload.bytesize
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_json
|
42
|
+
hsh = { type: self.class.to_s, app: app, device: device, alert: alert, badge: badge,
|
43
|
+
sound: sound, expiry: expiry, attributes_for_device: attributes_for_device }
|
44
|
+
MultiJson.dump(hsh)
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def as_json
|
50
|
+
json = ActiveSupport::OrderedHash.new
|
51
|
+
json['aps'] = ActiveSupport::OrderedHash.new
|
52
|
+
json['aps']['alert'] = alert if alert
|
53
|
+
json['aps']['badge'] = badge if badge
|
54
|
+
json['aps']['sound'] = sound if sound
|
55
|
+
attributes_for_device.each { |k, v| json[k.to_s] = v.to_s } if attributes_for_device
|
56
|
+
json
|
57
|
+
end
|
58
|
+
|
59
|
+
def max_payload_size
|
60
|
+
if payload_size > 256
|
61
|
+
errors.add(:payload, 'APN notification cannot be larger than 256 bytes. Try condensing your alert and device attributes.')
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'pushr/configuration_apns'
|
3
|
+
require 'pushr/message_apns'
|
4
|
+
require 'pushr/daemon'
|
5
|
+
require 'pushr/daemon/apns'
|
6
|
+
require 'pushr/daemon/apns_support/connection_apns'
|
7
|
+
|
8
|
+
describe Pushr::Daemon::ApnsSupport::ConnectionApns do
|
9
|
+
pending 'add test'
|
10
|
+
|
11
|
+
before(:each) do
|
12
|
+
Pushr::Core.configure do |config|
|
13
|
+
config.redis = ConnectionPool.new(size: 1, timeout: 1) { MockRedis.new }
|
14
|
+
end
|
15
|
+
|
16
|
+
logger = double('logger')
|
17
|
+
allow(logger).to receive(:info)
|
18
|
+
allow(logger).to receive(:error)
|
19
|
+
allow(logger).to receive(:warn)
|
20
|
+
Pushr::Daemon.logger = logger
|
21
|
+
|
22
|
+
allow(TCPSocket).to receive(:new).and_return(tcpsocket)
|
23
|
+
allow(OpenSSL::SSL::SSLSocket).to receive(:new).and_return(sslsocket)
|
24
|
+
end
|
25
|
+
|
26
|
+
let(:tcpsocket) { double('TCPSocket').as_null_object }
|
27
|
+
let(:sslsocket) { double('SSLSocket').as_null_object }
|
28
|
+
let(:certificate) { File.read(File.join(File.dirname(__FILE__), '..', '..', '..', 'support', 'cert_without_password.pem')) }
|
29
|
+
let(:config) do
|
30
|
+
Pushr::ConfigurationApns.new(app: 'app_name', connections: 2, enabled: true, certificate: certificate)
|
31
|
+
end
|
32
|
+
let(:message) do
|
33
|
+
hsh = { app: 'app_name', device: 'a' * 64, alert: 'message',
|
34
|
+
badge: 1, sound: '1.aiff', expiry: 24 * 60 * 60, attributes_for_device: { key: 'test' } }
|
35
|
+
Pushr::MessageApns.new(hsh)
|
36
|
+
end
|
37
|
+
let(:connection) { Pushr::Daemon::ApnsSupport::ConnectionApns.new(config, 1) }
|
38
|
+
|
39
|
+
describe 'sends a message' do
|
40
|
+
it 'succesful' do
|
41
|
+
expect(IO).to receive(:select).with([sslsocket], nil, nil, 0.2)
|
42
|
+
expect(sslsocket).to receive(:write).with(message.to_message)
|
43
|
+
connection.connect
|
44
|
+
connection.write(message)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'pushr/daemon'
|
3
|
+
require 'pushr/daemon/apns'
|
4
|
+
require 'pushr/daemon/apns_support/disconnection_error'
|
5
|
+
|
6
|
+
describe Pushr::Daemon::ApnsSupport::DisconnectionError do
|
7
|
+
let(:error) { Pushr::Daemon::ApnsSupport::DisconnectionError.new }
|
8
|
+
|
9
|
+
it 'returns a nil error code' do
|
10
|
+
expect(error.code).to be_nil
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'contains an error description' do
|
14
|
+
expect(error.description).not_to be_nil
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'returns a message' do
|
18
|
+
expect(error.message).not_to be_nil
|
19
|
+
expect(error.to_s).not_to be_nil
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'pushr/daemon'
|
3
|
+
require 'pushr/daemon/apns'
|
4
|
+
require 'pushr/daemon/apns_support/interruptible_sleep'
|
5
|
+
require 'pushr/daemon/apns_support/feedback_receiver'
|
6
|
+
|
7
|
+
describe Pushr::Daemon::ApnsSupport::FeedbackReceiver do
|
8
|
+
pending 'add test'
|
9
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'pushr/daemon'
|
3
|
+
require 'pushr/daemon/apns'
|
4
|
+
require 'pushr/daemon/apns_support/interruptible_sleep'
|
5
|
+
|
6
|
+
describe Pushr::Daemon::ApnsSupport::InterruptibleSleep do
|
7
|
+
let(:rd) { double(close: nil) }
|
8
|
+
let(:wr) { double(close: nil) }
|
9
|
+
|
10
|
+
subject { Pushr::Daemon::ApnsSupport::InterruptibleSleep.new }
|
11
|
+
|
12
|
+
it 'creates a new pipe' do
|
13
|
+
expect(IO).to receive(:pipe).and_return([rd, wr])
|
14
|
+
subject
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'selects on the reader' do
|
18
|
+
allow(IO).to receive(:pipe).and_return([rd, wr])
|
19
|
+
expect(IO).to receive(:select).with([rd], nil, nil, 1)
|
20
|
+
subject.sleep(1)
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'closes the writer' do
|
24
|
+
allow(IO).to receive(:pipe).and_return([rd, wr])
|
25
|
+
expect(rd).to receive(:close)
|
26
|
+
expect(wr).to receive(:close)
|
27
|
+
subject.close
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'returns false when timeout occurs' do
|
31
|
+
expect(subject.sleep(0.01)).to eql false
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'returns true when sleep does not timeout' do
|
35
|
+
subject.interrupt_sleep
|
36
|
+
expect(subject.sleep(0.01)).to eql true
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'pushr/configuration_apns'
|
3
|
+
|
4
|
+
describe Pushr::ConfigurationApns do
|
5
|
+
|
6
|
+
before(:each) do
|
7
|
+
Pushr::Core.configure do |config|
|
8
|
+
config.redis = ConnectionPool.new(size: 1, timeout: 1) { MockRedis.new }
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe 'all' do
|
13
|
+
it 'returns all configurations' do
|
14
|
+
expect(Pushr::Configuration.all).to eql([])
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe 'create' do
|
19
|
+
it 'should create a configuration' do
|
20
|
+
config = Pushr::ConfigurationApns.new(app: 'app_name', connections: 2, enabled: true, certificate: 'test', certificate_password: nil,
|
21
|
+
sandbox: true, feedback_poll: 100, skip_check_for_error: true)
|
22
|
+
expect(config.key).to eql('app_name:apns')
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe 'save' do
|
27
|
+
let(:config) do
|
28
|
+
Pushr::ConfigurationApns.new(app: 'app_name', connections: 2, enabled: true, certificate: 'test', certificate_password: nil,
|
29
|
+
sandbox: true, feedback_poll: 100, skip_check_for_error: true)
|
30
|
+
end
|
31
|
+
it 'should save a configuration' do
|
32
|
+
config.save
|
33
|
+
expect(Pushr::Configuration.all.count).to eql(1)
|
34
|
+
expect(Pushr::Configuration.all[0].class).to eql(Pushr::ConfigurationApns)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'pushr/feedback_apns'
|
3
|
+
|
4
|
+
describe Pushr::FeedbackApns do
|
5
|
+
|
6
|
+
before(:each) do
|
7
|
+
Pushr::Core.configure do |config|
|
8
|
+
config.redis = ConnectionPool.new(size: 1, timeout: 1) { MockRedis.new }
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe 'create' do
|
13
|
+
it 'should create a feedback' do
|
14
|
+
feedback = Pushr::FeedbackApns.new(app: 'app_name', device: 'a' * 64, follow_up: 'delete', failed_at: Time.now)
|
15
|
+
expect(feedback.app).to eql('app_name')
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe 'save' do
|
20
|
+
let(:feedback) { Pushr::FeedbackApns.new(app: 'app_name', device: 'a' * 64, follow_up: 'delete', failed_at: Time.now) }
|
21
|
+
it 'should save a feedback' do
|
22
|
+
feedback.save
|
23
|
+
feedback2 = Pushr::Feedback.next
|
24
|
+
expect(feedback2.class).to eql(Pushr::FeedbackApns)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'pushr/message_apns'
|
3
|
+
|
4
|
+
describe Pushr::MessageApns do
|
5
|
+
|
6
|
+
before(:each) do
|
7
|
+
Pushr::Core.configure do |config|
|
8
|
+
config.redis = ConnectionPool.new(size: 1, timeout: 1) { MockRedis.new }
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe 'next' do
|
13
|
+
it 'returns next message' do
|
14
|
+
expect(Pushr::Message.next('pushr:app_name:apns')).to eql(nil)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe 'save' do
|
19
|
+
let(:message) do
|
20
|
+
hsh = { app: 'app_name', device: 'a' * 64, alert: 'message',
|
21
|
+
badge: 1, sound: '1.aiff', expiry: 24 * 60 * 60, attributes_for_device: { key: 'test' } }
|
22
|
+
Pushr::MessageApns.new(hsh)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should return true' do
|
26
|
+
expect(message.save).to eql true
|
27
|
+
end
|
28
|
+
it 'should save a message' do
|
29
|
+
message.save
|
30
|
+
expect(Pushr::Message.next('pushr:app_name:apns')).to be_kind_of(Pushr::MessageApns)
|
31
|
+
end
|
32
|
+
it 'should respond to to_message' do
|
33
|
+
expect(message.to_message).to be_kind_of(String)
|
34
|
+
end
|
35
|
+
|
36
|
+
# TODO: add more tests
|
37
|
+
end
|
38
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
2
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
|
+
# Require this file using `require "spec_helper"` to ensure that it is only
|
4
|
+
# loaded once.
|
5
|
+
|
6
|
+
require 'simplecov'
|
7
|
+
require 'coveralls'
|
8
|
+
|
9
|
+
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
|
10
|
+
SimpleCov::Formatter::HTMLFormatter,
|
11
|
+
Coveralls::SimpleCov::Formatter
|
12
|
+
]
|
13
|
+
SimpleCov.start
|
14
|
+
|
15
|
+
require 'pushr/core'
|
16
|
+
require 'mock_redis'
|
17
|
+
|
18
|
+
Dir['./spec/support/**/*.rb'].sort.each { |f| require f }
|
19
|
+
|
20
|
+
RSpec.configure do |config|
|
21
|
+
config.raise_errors_for_deprecations!
|
22
|
+
config.run_all_when_everything_filtered = true
|
23
|
+
# config.filter_run :focus
|
24
|
+
|
25
|
+
# Run specs in random order to surface order dependencies. If you find an
|
26
|
+
# order dependency and want to debug it, you can fix the order by providing
|
27
|
+
# the seed, which is printed after each run.
|
28
|
+
# --seed 1234
|
29
|
+
config.order = 'random'
|
30
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
Bag Attributes
|
2
|
+
localKeyID: A4 1A DB 3E 3E 45 D9 C7 51 1E E6 DC 4E BC 29 19 E4 22 95 35
|
3
|
+
subject=/C=AU/ST=Some-State/O=Internet Widgits Pty Ltd
|
4
|
+
issuer=/C=UK/ST=Some-State/O=Internet Widgits Pty Ltd
|
5
|
+
-----BEGIN CERTIFICATE-----
|
6
|
+
MIIE/jCCAuYCAQEwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCVUsxEzARBgNV
|
7
|
+
BAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0
|
8
|
+
ZDAeFw0xMjEyMjgxMzU4NTdaFw0xNDEyMjgxMzU4NTdaMEUxCzAJBgNVBAYTAkFV
|
9
|
+
MRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRz
|
10
|
+
IFB0eSBMdGQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDYCSfYP0P7
|
11
|
+
F+JbxUHYsl/9Plr2bFZGMBygqs5RWpw1X977p4FqcNtnRmGENQ/I5vE3KkAKCAlS
|
12
|
+
gmus7CjB9/FVzoJq65cT3wBkcqAAzPgIQuI0UjP+rLktV84eUup7Pt6f/Bmt/fBj
|
13
|
+
h3fvf80nBQcdK2ztu1xtTs7EsRgyudUDDEIUZw9ddK21RbCMYE0PFY0QapNVvevs
|
14
|
+
8lDVUJX60bZqVqzhvlTzmEaBF+E66J/DhHuhcWZV588hVKjHMNED9Aq4PxRy78vI
|
15
|
+
54v1buX2Y9C6dHfIzfu7zz0Zv5NAY1BZKpaglVGKArOrX+K6O7Bq+DwckTm1AbUf
|
16
|
+
pQqi10ghveOtVrm4GXJ4OYW8ohdYKMFjzwhgTTr/NQ+EVlcZ+8AOPVPPJk0HgPu/
|
17
|
+
eMNiLytvcSnB09OzIUAzcxOof2a1zfxn7aPzBTEC6kkoDJC/BDG8wxySUz0zRyyM
|
18
|
+
jUN2+J1mBJZX1h1sM/4ycAzsX1EYm2II5GGJaFngiV45Qv7wTC7W31kvih7FsdUX
|
19
|
+
rEYMkevB5AFPUtIvzevLUObLbPW4yWvU2CMcKLZdIaPTvtPMba91t1YOdufpPRDd
|
20
|
+
HTmT42h0aUWDSrcWDAuZPPPqEIQNjRVCudtHtobZDeaUfvbx05CvypyuNFaPonuQ
|
21
|
+
l5h0LpZWPetvfsrOlMxPm2kfVcxm8mWjzQIDAQABMA0GCSqGSIb3DQEBBQUAA4IC
|
22
|
+
AQBiwdPfLU0kjbU1hM6grUHduLqOPDqLT5gefGRrL5fDqb86G08+Wz+Tnn1YmH+7
|
23
|
+
rfiQhCrdi4zxRv6ZEaKZeEm/q1UMttuUdXuzd25BEEtav4R0POdR+H1q3trlS0ol
|
24
|
+
EcWlAbRgVaT2tTyKGAW54fH8vZPqS6IP+mXIzfaOFECPEgAO8BL8t7hBDpkL4ASO
|
25
|
+
HbU7ktYamsr6PAei3kXAnJ1thXyQqrhelwLrQJyM8RVOJYUgVbl6Fpdtgu7YQF5G
|
26
|
+
kxTAfshSmDCQkf+tbUEs44rZ6BZ2TWnbXjQGkRntHcMCP/1rjsPPdX3oeZ7P3jMQ
|
27
|
+
XER3drdm1mOiSdDUKbem9GzQ9Dx7WkwKNLYAZ+IWzVRACzGxnkxXyxOEFIgesDgg
|
28
|
+
RIhczN+eLIR8iwcUxEVKFmmsbEIve3Uh1/NE6xGudbfZDNfhyOhiNYIBnQqVkk8l
|
29
|
+
c3gw2UDR3lXTayiiXhK2l1etsyxtYncT3pgDsCe72RODrGKbASt3FzfBbalzN0GG
|
30
|
+
9tiPSNtGqCch9q4eHfViUh9s3+8n4bknAYcwzQ96+gMEn8PUVtDBv9F8Kxffn/Jt
|
31
|
+
XMWKX76nTVuAuCLkXxrKwc01lq8SeuvCH650xsv0LBvxj9h6vR34vHGrj0C3sH2E
|
32
|
+
VQpelKNv8IEwkSiQcwDtU8H0jaPJNqmYlkrxasSrSeg6PA==
|
33
|
+
-----END CERTIFICATE-----
|
34
|
+
Bag Attributes
|
35
|
+
localKeyID: A4 1A DB 3E 3E 45 D9 C7 51 1E E6 DC 4E BC 29 19 E4 22 95 35
|
36
|
+
Key Attributes: <No Attributes>
|
37
|
+
-----BEGIN RSA PRIVATE KEY-----
|
38
|
+
Proc-Type: 4,ENCRYPTED
|
39
|
+
DEK-Info: DES-EDE3-CBC,874F2C982467BF08
|
40
|
+
|
41
|
+
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
|
90
|
+
-----END RSA PRIVATE KEY-----
|
@@ -0,0 +1,59 @@
|
|
1
|
+
Bag Attributes
|
2
|
+
friendlyName: test certificate
|
3
|
+
localKeyID: 00 93 8F E4 A3 C3 75 64 3D 7E EA 14 0B 0A EA DD 15 85 8A D5
|
4
|
+
subject=/CN=test certificate/O=Example/OU=Example/ST=QLD/C=AU/L=Example/emailAddress=user@example.com
|
5
|
+
issuer=/CN=test certificate/O=Example/OU=Example/ST=QLD/C=AU/L=Example/emailAddress=user@example.com
|
6
|
+
-----BEGIN CERTIFICATE-----
|
7
|
+
MIID5jCCAs6gAwIBAgIBATALBgkqhkiG9w0BAQswgY0xGTAXBgNVBAMMEHRlc3Qg
|
8
|
+
Y2VydGlmaWNhdGUxEDAOBgNVBAoMB0V4YW1wbGUxEDAOBgNVBAsMB0V4YW1wbGUx
|
9
|
+
DDAKBgNVBAgMA1FMRDELMAkGA1UEBhMCQVUxEDAOBgNVBAcMB0V4YW1wbGUxHzAd
|
10
|
+
BgkqhkiG9w0BCQEWEHVzZXJAZXhhbXBsZS5jb20wHhcNMTIwOTA5MDMxODMyWhcN
|
11
|
+
MjIwOTA3MDMxODMyWjCBjTEZMBcGA1UEAwwQdGVzdCBjZXJ0aWZpY2F0ZTEQMA4G
|
12
|
+
A1UECgwHRXhhbXBsZTEQMA4GA1UECwwHRXhhbXBsZTEMMAoGA1UECAwDUUxEMQsw
|
13
|
+
CQYDVQQGEwJBVTEQMA4GA1UEBwwHRXhhbXBsZTEfMB0GCSqGSIb3DQEJARYQdXNl
|
14
|
+
ckBleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKF+
|
15
|
+
UDsN1sLen8g+97PNTiWju9+wkSv+H5rQlvb6YFLPx11YvqpK8ms6kFU1OmWeLfmh
|
16
|
+
cpsT+bZtKupC7aGPoSG3RXzzf/YUMgs/ZSXA0idZHA6tkReAEzIX6jL5otfPWbaP
|
17
|
+
luCTUoVMeP4u9ywk628zlqh9IQHC1Agl0R1xGCpULDk8kn1gPyEisl38wI5aDbzy
|
18
|
+
6lYQGNUKOqt1xfVjtIFe/jyY/v0sxFjIJlRLcAFBuJx4sRV+PwRBkusOQtYwcwpI
|
19
|
+
loMxJj+GQe66ueATW81aC4iOU66DAFFEuGzwIwm3bOilimGGQbGb92F339RfmSOo
|
20
|
+
TPAvVhsakI3mzESb4lkCAwEAAaNRME8wDgYDVR0PAQH/BAQDAgeAMCAGA1UdJQEB
|
21
|
+
/wQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAbBgNVHREEFDASgRB1c2VyQGV4YW1w
|
22
|
+
bGUuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQA5UbNR+83ZdI2DiaB4dRmy0V5RDAqJ
|
23
|
+
k9+QskcTV4gBTjsOBS46Dw1tI6iTrfTyjYJdnyH0Y2Y2YVWBnvtON41UCZak+4ed
|
24
|
+
/IqyzU0dtfZ+frWa0RY4reyl80TwqnzyJfni0nDo4zGGvz70cxyaz2u1BWqwLjqb
|
25
|
+
dh8Dxvt+aHW2MQi0iGKh/HNbgwVanR4+ubNwziK9sR1Rnq9MkHWtwBw16SXQG6ao
|
26
|
+
SZKASWNaH8VL08Zz0E98cwd137UJkPsldCwJ8kHR5OzkcjPdXvnGD3d64yy2TC1Z
|
27
|
+
Gy1Aazt98wPcTYBytlhK8Rvzg9OoY9QmsdpmWxz1ZCXECJNqCa3IKsqO
|
28
|
+
-----END CERTIFICATE-----
|
29
|
+
Bag Attributes
|
30
|
+
friendlyName: test certificate
|
31
|
+
localKeyID: 00 93 8F E4 A3 C3 75 64 3D 7E EA 14 0B 0A EA DD 15 85 8A D5
|
32
|
+
Key Attributes: <No Attributes>
|
33
|
+
-----BEGIN RSA PRIVATE KEY-----
|
34
|
+
MIIEpQIBAAKCAQEAoX5QOw3Wwt6fyD73s81OJaO737CRK/4fmtCW9vpgUs/HXVi+
|
35
|
+
qkryazqQVTU6ZZ4t+aFymxP5tm0q6kLtoY+hIbdFfPN/9hQyCz9lJcDSJ1kcDq2R
|
36
|
+
F4ATMhfqMvmi189Zto+W4JNShUx4/i73LCTrbzOWqH0hAcLUCCXRHXEYKlQsOTyS
|
37
|
+
fWA/ISKyXfzAjloNvPLqVhAY1Qo6q3XF9WO0gV7+PJj+/SzEWMgmVEtwAUG4nHix
|
38
|
+
FX4/BEGS6w5C1jBzCkiWgzEmP4ZB7rq54BNbzVoLiI5TroMAUUS4bPAjCbds6KWK
|
39
|
+
YYZBsZv3YXff1F+ZI6hM8C9WGxqQjebMRJviWQIDAQABAoIBAQCTiLIDQUFSBdAz
|
40
|
+
QFNLD+S0vkCEuunlJuP4q1c/ir006l1YChsluBJ/o6D4NwiCjV+zDquEwVsALftm
|
41
|
+
yH4PewfZpXT2Ef508T5GyEO/mchj6iSXxDkpHvhqay6qIyWBwwxSnBtaTzy0Soi+
|
42
|
+
rmlhCtmLXbXld2sQEM1kJChGnWtWPtvSyrn+mapNPZviGRtgRNK+YsrAti1nUext
|
43
|
+
2syO5mTdHf1D8GR7I98OaX6odREuSocEV9PzfapWZx2GK5tvRiS1skiug5ciieTd
|
44
|
+
Am5/C+bb31h4drFslihLb5BRGO5SFQJvMJL2Sx1f19BCC4XikS01P4/zZbxQNq79
|
45
|
+
kxEQuDGBAoGBANP4pIYZ5xshCkx7cTYqmxzWLClGKE2S7Oa8N89mtOwfmqT9AFun
|
46
|
+
t9Us9Ukbi8BaKlKhGpQ1HlLf/KVcpyW0x2qLou6AyIWYH+/5VaR3graNgUnzpK9f
|
47
|
+
1F5HoaNHbhlAoebqhzhASFlJI2aqUdQjdOv73z+s9szJU4gpILNwGDFnAoGBAMMJ
|
48
|
+
j+vIxtG9J2jldyoXzpg5mbMXSj9u/wFLBVdjXWyOoiqVMMBto53RnoqAom7Ifr9D
|
49
|
+
49LxRAT1Q3l4vs/YnM3ziMsIg2vQK1EbrLsY9OnD/kvPaLXOlNIOdfLM8UeVWZMc
|
50
|
+
I4LPbbZrhv/7CC8RjbRhMoWWdGYPvxmvD6V4ZDY/AoGBALoI6OxA45Htx4okdNHj
|
51
|
+
RstiNNPsnQaoQn6nBhxiubraafEPkzbd1fukP4pwQJELEUX/2sHkdL6rkqLW1GPF
|
52
|
+
a5dZAiBsqpCFWNJWdBGqSfBJ9QSgbxLz+gDcwUH6OOi0zuNJRm/aCyVBiW5bYQHc
|
53
|
+
NIvAPMk31ksZDtTbs7WIVdNVAoGBALZ1+KWNxKqs+fSBT5UahpUUtfy8miJz9a7A
|
54
|
+
/3M8q0cGvSF3Rw+OwpW/aEGMi+l2OlU27ykFuyukRAac9m296RwnbF79TO2M5ylO
|
55
|
+
6a5zb5ROXlWP6RbE96b4DlIidssQJqegmHwlEC+rsrVBpOtb0aThlYEyOxzMOGyP
|
56
|
+
wOR9l8rDAoGADZ4TUHFM6VrvPlUZBkGbqiyXH9IM/y9JWk+22JQCEGnM6RFZemSs
|
57
|
+
jxWqQiPAdJtb3xKryJSCMtFPH9azedoCrSgaMflJ1QgoXgpiKZyoEXWraVUggh/0
|
58
|
+
CEavgZcTZ6SvMuayqJdGGB+zb1V8XwXMtCjApR/kTm47DjxO4DmpOPs=
|
59
|
+
-----END RSA PRIVATE KEY-----
|
metadata
ADDED
@@ -0,0 +1,231 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pushr-apns
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0.pre.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tom Pesman
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-05-09 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: multi_json
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: pushr-core
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: activemodel
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 3.0.0.beta2
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 3.0.0.beta2
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: guard
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: guard-rspec
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: mock_redis
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: simplecov
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: rake
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: coveralls
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: rubocop
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
description: APNS support for the modular push daemon.
|
168
|
+
email:
|
169
|
+
- tom@tnux.net
|
170
|
+
executables: []
|
171
|
+
extensions: []
|
172
|
+
extra_rdoc_files: []
|
173
|
+
files:
|
174
|
+
- lib/pushr-apns/version.rb
|
175
|
+
- lib/pushr/apns.rb
|
176
|
+
- lib/pushr/configuration_apns.rb
|
177
|
+
- lib/pushr/daemon/apns.rb
|
178
|
+
- lib/pushr/daemon/apns_support/connection_apns.rb
|
179
|
+
- lib/pushr/daemon/apns_support/disconnection_error.rb
|
180
|
+
- lib/pushr/daemon/apns_support/feedback_receiver.rb
|
181
|
+
- lib/pushr/daemon/apns_support/interruptible_sleep.rb
|
182
|
+
- lib/pushr/feedback_apns.rb
|
183
|
+
- lib/pushr/message_apns.rb
|
184
|
+
- README.md
|
185
|
+
- MIT-LICENSE
|
186
|
+
- spec/lib/pushr/apns_spec.rb
|
187
|
+
- spec/lib/pushr/apns_support/connection_apns_spec.rb
|
188
|
+
- spec/lib/pushr/apns_support/disconnection_error_spec.rb
|
189
|
+
- spec/lib/pushr/apns_support/feedback_receiver_spec.rb
|
190
|
+
- spec/lib/pushr/apns_support/interruptible_sleep_spec.rb
|
191
|
+
- spec/lib/pushr/configuration_apns_spec.rb
|
192
|
+
- spec/lib/pushr/feedback_apns_spec.rb
|
193
|
+
- spec/lib/pushr/message_apns_spec.rb
|
194
|
+
- spec/spec_helper.rb
|
195
|
+
- spec/support/cert_with_password.pem
|
196
|
+
- spec/support/cert_without_password.pem
|
197
|
+
homepage: https://github.com/tompesman/pushr-apns
|
198
|
+
licenses: []
|
199
|
+
metadata: {}
|
200
|
+
post_install_message:
|
201
|
+
rdoc_options: []
|
202
|
+
require_paths:
|
203
|
+
- lib
|
204
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
205
|
+
requirements:
|
206
|
+
- - ">="
|
207
|
+
- !ruby/object:Gem::Version
|
208
|
+
version: '0'
|
209
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
210
|
+
requirements:
|
211
|
+
- - ">"
|
212
|
+
- !ruby/object:Gem::Version
|
213
|
+
version: 1.3.1
|
214
|
+
requirements: []
|
215
|
+
rubyforge_project:
|
216
|
+
rubygems_version: 2.1.11
|
217
|
+
signing_key:
|
218
|
+
specification_version: 4
|
219
|
+
summary: APNS (iOS/Apple) part of the modular push daemon.
|
220
|
+
test_files:
|
221
|
+
- spec/lib/pushr/apns_spec.rb
|
222
|
+
- spec/lib/pushr/apns_support/connection_apns_spec.rb
|
223
|
+
- spec/lib/pushr/apns_support/disconnection_error_spec.rb
|
224
|
+
- spec/lib/pushr/apns_support/feedback_receiver_spec.rb
|
225
|
+
- spec/lib/pushr/apns_support/interruptible_sleep_spec.rb
|
226
|
+
- spec/lib/pushr/configuration_apns_spec.rb
|
227
|
+
- spec/lib/pushr/feedback_apns_spec.rb
|
228
|
+
- spec/lib/pushr/message_apns_spec.rb
|
229
|
+
- spec/spec_helper.rb
|
230
|
+
- spec/support/cert_with_password.pem
|
231
|
+
- spec/support/cert_without_password.pem
|