pushr-apns 1.0.0.pre.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://travis-ci.org/9to5/pushr-apns.svg?branch=master)](https://travis-ci.org/9to5/pushr-apns)
|
4
|
+
[![Code Climate](https://codeclimate.com/github/9to5/pushr-apns.png)](https://codeclimate.com/github/9to5/pushr-apns)
|
5
|
+
[![Coverage Status](https://coveralls.io/repos/9to5/pushr-apns/badge.png)](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
|