ghazel-em-apn 0.0.3.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.
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