ghazel-em-apn 0.0.3.1

Sign up to get free protection for your applications and to get access to all the features.
data/script/test ADDED
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "rubygems"
4
+ require "bundler/setup"
5
+ require "em-apn"
6
+
7
+ ENV["APN_KEY"] = File.join(File.dirname(__FILE__), "..", "certs", "key.pem")
8
+ ENV["APN_CERT"] = File.join(File.dirname(__FILE__), "..", "certs", "cert.pem")
9
+
10
+ TOKEN = "fe9515ba7556cfdcfca6530235c95dff682fac765838e749a201a7f6cf3792e6"
11
+
12
+ def notify(client, queue)
13
+ if queue.empty?
14
+ EM.add_periodic_timer(1) { EM.stop }
15
+ else
16
+ queue.pop do |alert|
17
+ notification = EM::APN::Notification.new(TOKEN, :alert => alert)
18
+ client.deliver(notification)
19
+
20
+ interval = rand(20).to_f / 100.0
21
+ EM.add_timer(interval) { notify(client, queue) }
22
+ end
23
+ end
24
+ end
25
+
26
+ EM.run do
27
+ client = EM::APN::Client.new(:gateway => "127.0.0.1")
28
+ queue = EM::Queue.new
29
+ queue.push *((1..5).to_a)
30
+ queue.push "DISCONNECT"
31
+ queue.push *((6..10).to_a)
32
+ queue.push "DISCONNECT"
33
+ queue.push *((11..15).to_a)
34
+
35
+ EM.next_tick { notify(client, queue) }
36
+ end
@@ -0,0 +1,243 @@
1
+ require "spec_helper"
2
+
3
+ describe EventMachine::APN::Client do
4
+ def new_client(*args)
5
+ client = nil
6
+ EM.run_block {
7
+ client = EM.connect("localhost", 8888, EventMachine::APN::Client, *args)
8
+ }
9
+ client
10
+ end
11
+
12
+ describe ".new" do
13
+ it "creates a new client without a connection" do
14
+ client = EM::APN::Client.new
15
+ client.connection.should be_nil
16
+ client.feedback_connection.should be_nil
17
+ end
18
+
19
+ context "configuring the gateway" do
20
+ before do
21
+ ENV["APN_GATEWAY"] = nil
22
+ end
23
+
24
+ let(:options) { {:key => ENV["APN_KEY"], :cert => ENV["APN_CERT"]} }
25
+
26
+ it "defaults to Apple's sandbox (gateway.sandbox.push.apple.com)" do
27
+ client = EM::APN::Client.new
28
+ client.gateway.should == "gateway.sandbox.push.apple.com"
29
+ client.port.should == 2195
30
+ end
31
+
32
+ it "uses an environment variable for the gateway host (APN_GATEWAY) if specified" do
33
+ ENV["APN_GATEWAY"] = "localhost"
34
+
35
+ client = EM::APN::Client.new
36
+ client.gateway.should == "localhost"
37
+ client.port.should == 2195
38
+ end
39
+
40
+ it "switches to the production gateway if APN_ENV is set to 'production'" do
41
+ ENV["APN_ENV"] = "production"
42
+
43
+ client = EM::APN::Client.new
44
+ client.gateway.should == "gateway.push.apple.com"
45
+ client.port.should == 2195
46
+
47
+ ENV["APN_ENV"] = nil
48
+ end
49
+
50
+ it "takes arguments for the gateway and port" do
51
+ client = EM::APN::Client.new(:gateway => "localhost", :port => 3333)
52
+ client.gateway.should == "localhost"
53
+ client.port.should == 3333
54
+ end
55
+ end
56
+
57
+ context "configuring SSL" do
58
+ it "falls back to environment variables for key and cert (APN_KEY and APN_CERT) if they are unspecified" do
59
+ ENV["APN_KEY"] = "path/to/key.pem"
60
+ ENV["APN_CERT"] = "path/to/cert.pem"
61
+
62
+ client = EM::APN::Client.new
63
+ client.key.should == "path/to/key.pem"
64
+ client.cert.should == "path/to/cert.pem"
65
+ end
66
+
67
+ it "takes arguments for the key and cert" do
68
+ client = EM::APN::Client.new(:key => "key.pem", :cert => "cert.pem")
69
+ client.key.should == "key.pem"
70
+ client.cert.should == "cert.pem"
71
+ end
72
+ end
73
+ end
74
+
75
+ describe "#connect" do
76
+ it "creates a connection to the gateway" do
77
+ client = EM::APN::Client.new
78
+ client.connection.should be_nil
79
+
80
+ EM.run_block { client.connect }
81
+ client.connection.should be_an_instance_of(EM::APN::Connection)
82
+ end
83
+
84
+ it "passes the client to the new connection" do
85
+ client = EM::APN::Client.new
86
+ connection = double(EM::APN::Connection).as_null_object
87
+
88
+ EM::APN::Connection.should_receive(:new).with(instance_of(Fixnum), client).and_return(connection)
89
+ EM.run_block { client.connect }
90
+ end
91
+ end
92
+
93
+ describe "#deliver" do
94
+ let(:token) { "fe9515ba7556cfdcfca6530235c95dff682fac765838e749a201a7f6cf3792e6" }
95
+
96
+ it "sends a Notification object" do
97
+ notification = EM::APN::Notification.new(token, :alert => "Hello world")
98
+ delivered = nil
99
+
100
+ EM.run_block do
101
+ client = EM::APN::Client.new
102
+ client.connect
103
+ client.connection.stub(:send_data).and_return do |data|
104
+ delivered = data.unpack("cNNnH64na*")
105
+ nil
106
+ end
107
+ client.deliver(notification)
108
+ end
109
+
110
+ delivered[4].should == token
111
+ delivered[6].should == MultiJson.encode({:aps => {:alert => "Hello world"}})
112
+ end
113
+
114
+ it "logs a message" do
115
+ alert = "Hello world this is a long push notification to you"
116
+
117
+ test_log = StringIO.new
118
+ EM::APN.logger = Logger.new(test_log)
119
+
120
+ notification = EM::APN::Notification.new(token, "alert" => alert)
121
+
122
+ EM.run_block do
123
+ client = EM::APN::Client.new
124
+ client.deliver(notification)
125
+ end
126
+
127
+ test_log.rewind
128
+ test_log.read.should include("TOKEN=#{token} PAYLOAD=#{notification.payload.inspect}")
129
+ end
130
+
131
+ context "when the notification is not valid" do
132
+ it "raises an exception" do
133
+ notification = EM::APN::Notification.new(token)
134
+ notification.stub(:validate!).and_raise(RuntimeError.new("Boo"))
135
+
136
+ expect {
137
+ EM.run_block do
138
+ client = EM::APN::Client.new
139
+ client.deliver(notification)
140
+ end
141
+ }.to raise_error(RuntimeError)
142
+ end
143
+ end
144
+ end
145
+
146
+ describe "#on_error" do
147
+ it "sets a callback that is invoked when we receive data from Apple" do
148
+ error = nil
149
+
150
+ EM.run_block do
151
+ client = EM::APN::Client.new
152
+ client.connect
153
+ client.on_error { |e| error = e }
154
+ client.connection.receive_data([8, 8, 0].pack("ccN"))
155
+ end
156
+
157
+ error.should be
158
+
159
+ error.command.should == 8
160
+ error.status_code.should == 8
161
+ error.identifier.should == 0
162
+
163
+ error.description.should == "Invalid token"
164
+ error.to_s.should == "CODE=8 ID=0 DESC=Invalid token"
165
+ end
166
+ end
167
+
168
+ describe "#on_close" do
169
+ it "sets a callback that is invoked when the connection closes" do
170
+ called = false
171
+
172
+ EM.run_block do
173
+ client = EM::APN::Client.new
174
+ client.on_close { called = true }
175
+ client.connect
176
+ end
177
+
178
+ called.should be_true
179
+ end
180
+ end
181
+
182
+ describe "#on_open" do
183
+ before do
184
+ @server = TCPServer.new(EventMachine::APN::Client::PORT)
185
+ end
186
+
187
+ after do
188
+ @server.close
189
+ end
190
+
191
+ it "sets a callback that is invoked when the connection is opened" do
192
+ called = false
193
+
194
+ EM.run_block do
195
+ client = EM::APN::Client.new
196
+ client.on_open { called = true }
197
+ client.connect
198
+ end
199
+
200
+ called.should be_true
201
+ end
202
+ end
203
+
204
+ describe "#connect_feedback" do
205
+ it "creates a connection to the feedback service" do
206
+ client = EM::APN::Client.new
207
+ client.feedback_connection.should be_nil
208
+
209
+ EM.run_block { client.connect_feedback }
210
+ client.feedback_connection.should be_an_instance_of(EM::APN::FeedbackConnection)
211
+ end
212
+
213
+ it "passes the client to the new connection" do
214
+ client = EM::APN::Client.new
215
+ connection = double(EM::APN::FeedbackConnection).as_null_object
216
+
217
+ EM::APN::FeedbackConnection.should_receive(:new).with(instance_of(Fixnum), client).and_return(connection)
218
+ EM.run_block { client.connect_feedback }
219
+ end
220
+ end
221
+
222
+ describe "#on_feedback" do
223
+ it "sets a callback that is invoked when we receive data from Apple" do
224
+ failed_attempt = nil
225
+
226
+
227
+ timestamp = Time.utc(1995, 12, 21)
228
+ device_token = 'fe15a27d5df3c34778defb1f4f3980265cc52c0c047682223be59fb68500a9a2'
229
+ tuple = [timestamp.to_i, 32, device_token].pack('NnH64')
230
+
231
+ EM.run_block do
232
+ client = EM::APN::Client.new
233
+ client.connect_feedback
234
+ client.on_feedback { |data | failed_attempt = data }
235
+ client.feedback_connection.receive_data(tuple)
236
+ end
237
+
238
+ failed_attempt.should be_an_instance_of(EM::APN::FailedDeliveryAttempt)
239
+ failed_attempt.device_token.should == device_token
240
+ failed_attempt.timestamp.should == timestamp
241
+ end
242
+ end
243
+ end
@@ -0,0 +1,182 @@
1
+ # encoding: UTF-8
2
+ require "spec_helper"
3
+
4
+ describe EventMachine::APN::Notification do
5
+ let(:token) { "fe9515ba7556cfdcfca6530235c95dff682fac765838e749a201a7f6cf3792e6" }
6
+
7
+ describe "#initialize" do
8
+ it "raises an exception if the token is blank" do
9
+ expect { EM::APN::Notification.new(nil) }.to raise_error
10
+ expect { EM::APN::Notification.new("") }.to raise_error
11
+ end
12
+
13
+ it "raises an exception if the token is less than or greater than 32 bytes" do
14
+ expect { EM::APN::Notification.new("0" * 63) }.to raise_error
15
+ expect { EM::APN::Notification.new("0" * 65) }.to raise_error
16
+ end
17
+ end
18
+
19
+ describe "#token" do
20
+ it "returns the token" do
21
+ notification = EM::APN::Notification.new(token)
22
+ notification.token.should == token
23
+ end
24
+ end
25
+
26
+ describe "#payload" do
27
+ it "returns aps properties encoded as JSON" do
28
+ notification = EM::APN::Notification.new(token, {
29
+ :alert => "Hello world",
30
+ :badge => 10,
31
+ :sound => "ding.aiff"
32
+ })
33
+ payload = MultiJson.decode(notification.payload)
34
+ payload["aps"]["alert"].should == "Hello world"
35
+ payload["aps"]["badge"].should == 10
36
+ payload["aps"]["sound"].should == "ding.aiff"
37
+ end
38
+
39
+ it "returns custom properties as well" do
40
+ notification = EM::APN::Notification.new(token, {}, {:line => "I'm super bad"})
41
+ payload = MultiJson.decode(notification.payload)
42
+ payload["line"].should == "I'm super bad"
43
+ end
44
+
45
+ it "handles string keys" do
46
+ notification = EM::APN::Notification.new(token,
47
+ {
48
+ "alert" => "Hello world",
49
+ "badge" => 10,
50
+ "sound" => "ding.aiff"
51
+ },
52
+ {
53
+ "custom" => "param"
54
+ }
55
+ )
56
+ payload = MultiJson.decode(notification.payload)
57
+ payload["aps"]["alert"].should == "Hello world"
58
+ payload["aps"]["badge"].should == 10
59
+ payload["aps"]["sound"].should == "ding.aiff"
60
+ payload["custom"].should == "param"
61
+ end
62
+ end
63
+
64
+ describe "#data" do
65
+ it "returns the enhanced notification in the supported binary format" do
66
+ notification = EM::APN::Notification.new(token, {:alert => "Hello world"})
67
+ data = notification.data.unpack("cNNnH64na*")
68
+ data[4].should == token
69
+ data[5].should == notification.payload.length
70
+ data[6].should == notification.payload
71
+ end
72
+
73
+ it "handles UTF-8 payloads" do
74
+ string = "✓ Please"
75
+ notification = EM::APN::Notification.new(token, {:alert => string})
76
+ data = notification.data.unpack("cNNnH64na*")
77
+ data[4].should == token
78
+ data[5].should == [notification.payload].pack("a*").size
79
+ data[6].force_encoding("UTF-8").should == notification.payload
80
+ end
81
+
82
+ it "defaults the identifier and expiry to 0" do
83
+ notification = EM::APN::Notification.new(token, {:alert => "Hello world"})
84
+ data = notification.data.unpack("cNNnH64na*")
85
+ data[1].should == 0 # Identifier
86
+ data[2].should == 0 # Expiry
87
+ end
88
+ end
89
+
90
+ describe "#validate!" do
91
+ it "raises PayloadTooLarge error if PAYLOAD_MAX_BYTES exceeded" do
92
+ notification = EM::APN::Notification.new(token, {:alert => "X" * 512})
93
+
94
+ lambda {
95
+ notification.validate!
96
+ }.should raise_error(EM::APN::Notification::PayloadTooLarge)
97
+ end
98
+ end
99
+
100
+ describe "#identifier=" do
101
+ it "sets the identifier, which is returned in the binary data" do
102
+ notification = EM::APN::Notification.new(token)
103
+ notification.identifier = 12345
104
+ notification.identifier.should == 12345
105
+
106
+ data = notification.data.unpack("cNNnH64na*")
107
+ data[1].should == notification.identifier
108
+ end
109
+
110
+ it "converts everything to an integer" do
111
+ notification = EM::APN::Notification.new(token)
112
+ notification.identifier = "12345"
113
+ notification.identifier.should == 12345
114
+ end
115
+
116
+ it "can be set in the initializer" do
117
+ notification = EM::APN::Notification.new(token, {}, {}, {:identifier => 12345})
118
+ notification.identifier.should == 12345
119
+ end
120
+ end
121
+
122
+ describe "#expiry=" do
123
+ it "sets the expiry, which is returned in the binary data" do
124
+ epoch = Time.now.to_i
125
+ notification = EM::APN::Notification.new(token)
126
+ notification.expiry = epoch
127
+ notification.expiry.should == epoch
128
+
129
+ data = notification.data.unpack("cNNnH64na*")
130
+ data[2].should == epoch
131
+ end
132
+
133
+ it "can be set in the initializer" do
134
+ epoch = Time.now.to_i
135
+ notification = EM::APN::Notification.new(token, {}, {}, {:expiry => epoch})
136
+ notification.expiry.should == epoch
137
+ end
138
+ end
139
+
140
+ describe "#truncate_alert!" do
141
+ context "when the data size would exceed the APN limit" do
142
+ it "truncates the alert" do
143
+ notification = EM::APN::Notification.new(token, { "alert" => "X" * 300 })
144
+ notification.data.size.should be > EM::APN::Notification::DATA_MAX_BYTES
145
+
146
+ notification.truncate_alert!
147
+ notification.data.size.should be <= EM::APN::Notification::DATA_MAX_BYTES
148
+ parsed_payload = MultiJson.decode(notification.payload)
149
+ parsed_payload["aps"]["alert"].size.should == 191
150
+ end
151
+
152
+ it "truncates the alert properly when symbol payload keys are used" do
153
+ notification = EM::APN::Notification.new(token, { "alert" => "X" * 300 })
154
+ notification.truncate_alert!
155
+ notification.data.size.should be <= EM::APN::Notification::DATA_MAX_BYTES
156
+ end
157
+
158
+ it "truncates the alert properly when it is JSON serialized into a different size" do
159
+ notification = EM::APN::Notification.new(token, { "alert" => '"' * 300 })
160
+ notification.truncate_alert!
161
+ parsed_payload = MultiJson.decode(notification.payload)
162
+ parsed_payload["aps"]["alert"].size.should == 95
163
+ end
164
+
165
+ it "truncates the alert properly for multi-byte Unicode characters" do
166
+ notification = EM::APN::Notification.new(token, { "alert" => [57352].pack('U') * 500 })
167
+ notification.truncate_alert!
168
+ parsed_payload = MultiJson.decode(notification.payload)
169
+ parsed_payload["aps"]["alert"].size.should == 63
170
+ end
171
+ end
172
+
173
+ context "when the data size would not exceed the APN limit" do
174
+ it "does not change the alert" do
175
+ notification = EM::APN::Notification.new(token, { "alert" => "Hello world" })
176
+ notification.truncate_alert!
177
+ parsed_payload = MultiJson.decode(notification.payload)
178
+ parsed_payload["aps"]["alert"].should == "Hello world"
179
+ end
180
+ end
181
+ end
182
+ end
@@ -0,0 +1,38 @@
1
+ require "spec_helper"
2
+
3
+ describe Hash do
4
+ describe "stringify_keys!" do
5
+ it "returns the hash" do
6
+ {}.stringify_keys!.should eq({})
7
+ end
8
+
9
+ it "converts keys into strings" do
10
+ hash = {
11
+ :symbol => "value",
12
+ 1 => "value",
13
+ [] => "value"
14
+ }
15
+
16
+ hash.stringify_keys!
17
+
18
+ hash.keys.should =~ ["symbol", "1", "[]"]
19
+ end
20
+
21
+ it "leaves string keys untouched" do
22
+ string = stub
23
+ string.should_receive(:kind_of?).with(String).and_return(true)
24
+
25
+ symbol = stub(to_s: "symbol")
26
+ symbol.should_receive(:kind_of?).with(String).and_return(false)
27
+
28
+ hash = {
29
+ string => "value",
30
+ symbol => "value"
31
+ }
32
+
33
+ hash.stringify_keys!
34
+
35
+ hash.keys.should =~ ["symbol", string]
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,13 @@
1
+ require "rubygems"
2
+ require "bundler/setup"
3
+ Bundler.require :default, :development
4
+
5
+ RSpec.configure do |config|
6
+ config.before(:each) do
7
+ ENV["APN_KEY"] = "spec/support/certs/key.pem"
8
+ ENV["APN_CERT"] = "spec/support/certs/cert.pem"
9
+ ENV["APN_GATEWAY"] = "127.0.0.1"
10
+
11
+ EM::APN.logger = Logger.new("/dev/null")
12
+ end
13
+ end
@@ -0,0 +1,17 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIICqjCCAhOgAwIBAgIJAO3gD8Fv66MhMA0GCSqGSIb3DQEBBQUAMEMxCzAJBgNV
3
+ BAYTAlVTMREwDwYDVQQIEwhOZXcgWW9yazERMA8GA1UEBxMITmV3IFlvcmsxDjAM
4
+ BgNVBAoTBUR1bW15MB4XDTExMDYyMTE5MDAyOFoXDTIxMDYxODE5MDAyOFowQzEL
5
+ MAkGA1UEBhMCVVMxETAPBgNVBAgTCE5ldyBZb3JrMREwDwYDVQQHEwhOZXcgWW9y
6
+ azEOMAwGA1UEChMFRHVtbXkwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMsR
7
+ 05wNdOiFXil3/r4xGLXc7MYwU2LiZbnXAqaoGnLv3W8cN7i/0i63y8YYITNWm3ji
8
+ lMiOkj2+TNVE/PiFizFBqhT28V/AsfOsiJrYQ4GXwLwJTsgDGTwAJgov0yPIffhK
9
+ n9jU7NLNJjhrj0rVD++eNOT8dunvYRlI5S8lJWCNAgMBAAGjgaUwgaIwHQYDVR0O
10
+ BBYEFISS0qC22So8BtQYTT4n/iLdYnZnMHMGA1UdIwRsMGqAFISS0qC22So8BtQY
11
+ TT4n/iLdYnZnoUekRTBDMQswCQYDVQQGEwJVUzERMA8GA1UECBMITmV3IFlvcmsx
12
+ ETAPBgNVBAcTCE5ldyBZb3JrMQ4wDAYDVQQKEwVEdW1teYIJAO3gD8Fv66MhMAwG
13
+ A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAFBGom218pNCB1zYxhDDtOiYC
14
+ DyU9rZkLXxMVjSHCvyBtQ3EC/21Zog05LSBMvOHFmfzPbyX/IYxSglJLJlQBzGtP
15
+ trVx1slmva3/QoKYwAKdAe5oSY8eDqejBpKO12maSGsPwbw3oREmudeWkrFFVAlP
16
+ hlkmwpq7901kIg4CCe8=
17
+ -----END CERTIFICATE-----
@@ -0,0 +1,15 @@
1
+ -----BEGIN RSA PRIVATE KEY-----
2
+ MIICXQIBAAKBgQDLEdOcDXTohV4pd/6+MRi13OzGMFNi4mW51wKmqBpy791vHDe4
3
+ v9Iut8vGGCEzVpt44pTIjpI9vkzVRPz4hYsxQaoU9vFfwLHzrIia2EOBl8C8CU7I
4
+ Axk8ACYKL9MjyH34Sp/Y1OzSzSY4a49K1Q/vnjTk/Hbp72EZSOUvJSVgjQIDAQAB
5
+ AoGBAIHk8Ef076BAlz/Naty7yQOjwqzvgpdRHCLo3uA9zVVSC4GkOhxqTwblOGqJ
6
+ SsttDdwgi21SjUcDcGBHVc2elq6SNx9A0mwhb9uPBUI74pJdbrN3/Ue1UnaKy7KZ
7
+ 1PGlMi8GtBL5cn5+NCs/IJUZIIp3oNxURJTF54tun6ygOeddAkEA74rcrXRvFAab
8
+ mMwQuGlFCNU1ns6Zm19tBFBadTUJQl0cB0MAS9BKveXGOsxXVChR0JvDTnTnx5a4
9
+ SwKz3t/irwJBANkFeJdZy9Ve5mzzY3rt6sCorOPo9+lER4K01HB6h24vWB1DIFRE
10
+ YNNiK7YBVHrnb4mcKSdP93QXlYoAugP974MCQQDrVPACVI5ADVHV5j1S/tC8ocJg
11
+ 9zW/iBtxDoQf++/Ry+maVL+4u7SCJXf/EfuFiWr/V9ejf4Sp96+sucX+YtOvAkBs
12
+ cVts5aYBHMavsn8HMlOXqbGawRMAMOo62fk9qzx5RpcVKDHDadeoSOnmrIt2Tqdh
13
+ b/Lwffj8vbwvlWVeEUnZAkBwGrhn+BzcfsPLIQfyfC13c1oLJNTZ/ktQdiZIp9Sn
14
+ Zmaf4yBl3NekmgdNQUGdbPBEUl32ukVhOkfOhI2qZcbA
15
+ -----END RSA PRIVATE KEY-----
metadata ADDED
@@ -0,0 +1,121 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ghazel-em-apn
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3.1
5
+ platform: ruby
6
+ authors:
7
+ - Dave Yeu
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-05-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: eventmachine
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: 1.0.0.beta.3
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: 1.0.0.beta.3
27
+ - !ruby/object:Gem::Dependency
28
+ name: multi_json
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: 1.8.2
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: 1.8.2
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: 2.6.0
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: 2.6.0
55
+ description:
56
+ email:
57
+ - daveyeu@gmail.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - .gitignore
63
+ - .rspec
64
+ - Gemfile
65
+ - LICENSE
66
+ - README.md
67
+ - Rakefile
68
+ - certs/.gitignore
69
+ - em-apn.gemspec
70
+ - lib/em-apn.rb
71
+ - lib/em-apn/client.rb
72
+ - lib/em-apn/connection.rb
73
+ - lib/em-apn/error_response.rb
74
+ - lib/em-apn/failed_delivery_attempt.rb
75
+ - lib/em-apn/feedback_connection.rb
76
+ - lib/em-apn/log_message.rb
77
+ - lib/em-apn/notification.rb
78
+ - lib/em-apn/response.rb
79
+ - lib/em-apn/server.rb
80
+ - lib/em-apn/test_helper.rb
81
+ - lib/em-apn/version.rb
82
+ - lib/extensions/hash.rb
83
+ - script/push
84
+ - script/server
85
+ - script/test
86
+ - spec/em-apn/client_spec.rb
87
+ - spec/em-apn/notification_spec.rb
88
+ - spec/extensions/hash_spec.rb
89
+ - spec/spec_helper.rb
90
+ - spec/support/certs/cert.pem
91
+ - spec/support/certs/key.pem
92
+ homepage: ''
93
+ licenses: []
94
+ metadata: {}
95
+ post_install_message:
96
+ rdoc_options: []
97
+ require_paths:
98
+ - lib
99
+ required_ruby_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ required_rubygems_version: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - '>='
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
109
+ requirements: []
110
+ rubyforge_project: em-apn
111
+ rubygems_version: 2.0.14
112
+ signing_key:
113
+ specification_version: 4
114
+ summary: EventMachine-driven Apple Push Notifications
115
+ test_files:
116
+ - spec/em-apn/client_spec.rb
117
+ - spec/em-apn/notification_spec.rb
118
+ - spec/extensions/hash_spec.rb
119
+ - spec/spec_helper.rb
120
+ - spec/support/certs/cert.pem
121
+ - spec/support/certs/key.pem