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.
- checksums.yaml +7 -0
- data/.gitignore +6 -0
- data/.rspec +1 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +85 -0
- data/Rakefile +9 -0
- data/certs/.gitignore +0 -0
- data/em-apn.gemspec +24 -0
- data/lib/em-apn/client.rb +78 -0
- data/lib/em-apn/connection.rb +47 -0
- data/lib/em-apn/error_response.rb +34 -0
- data/lib/em-apn/failed_delivery_attempt.rb +18 -0
- data/lib/em-apn/feedback_connection.rb +43 -0
- data/lib/em-apn/log_message.rb +13 -0
- data/lib/em-apn/notification.rb +62 -0
- data/lib/em-apn/response.rb +13 -0
- data/lib/em-apn/server.rb +58 -0
- data/lib/em-apn/test_helper.rb +37 -0
- data/lib/em-apn/version.rb +5 -0
- data/lib/em-apn.rb +26 -0
- data/lib/extensions/hash.rb +13 -0
- data/script/push +29 -0
- data/script/server +17 -0
- data/script/test +36 -0
- data/spec/em-apn/client_spec.rb +243 -0
- data/spec/em-apn/notification_spec.rb +182 -0
- data/spec/extensions/hash_spec.rb +38 -0
- data/spec/spec_helper.rb +13 -0
- data/spec/support/certs/cert.pem +17 -0
- data/spec/support/certs/key.pem +15 -0
- metadata +121 -0
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
|
data/spec/spec_helper.rb
ADDED
@@ -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
|