imobile 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +3 -1
- data/Manifest +7 -1
- data/imobile.gemspec +5 -5
- data/lib/imobile/crypto_app_fprint.rb +1 -1
- data/lib/imobile/push_notification.rb +261 -0
- data/lib/imobile/validate_receipt.rb +1 -1
- data/lib/imobile.rb +1 -0
- data/test/push_notification_test.rb +177 -0
- data/testdata/apns_developer.p12 +0 -0
- data/testdata/apns_production.p12 +0 -0
- data/testdata/encoded_notification +0 -0
- data/testdata/sandbox_device_token +1 -0
- data/testdata/sandbox_device_token.bin +1 -0
- metadata +12 -3
data/CHANGELOG
CHANGED
data/Manifest
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
CHANGELOG
|
2
|
-
imobile.gemspec
|
3
2
|
lib/imobile/crypto_app_fprint.rb
|
3
|
+
lib/imobile/push_notification.rb
|
4
4
|
lib/imobile/validate_receipt.rb
|
5
5
|
lib/imobile.rb
|
6
6
|
LICENSE
|
@@ -8,7 +8,13 @@ Manifest
|
|
8
8
|
Rakefile
|
9
9
|
README
|
10
10
|
test/crypto_app_fprint_test.rb
|
11
|
+
test/push_notification_test.rb
|
11
12
|
test/validate_receipt_test.rb
|
13
|
+
testdata/apns_developer.p12
|
14
|
+
testdata/apns_production.p12
|
12
15
|
testdata/device_attributes.yml
|
16
|
+
testdata/encoded_notification
|
13
17
|
testdata/forged_sandbox_receipt
|
18
|
+
testdata/sandbox_device_token
|
19
|
+
testdata/sandbox_device_token.bin
|
14
20
|
testdata/valid_sandbox_receipt
|
data/imobile.gemspec
CHANGED
@@ -2,22 +2,22 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = %q{imobile}
|
5
|
-
s.version = "0.0.
|
5
|
+
s.version = "0.0.3"
|
6
6
|
|
7
7
|
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
8
|
s.authors = ["Victor Costan"]
|
9
|
-
s.date = %q{2009-07-
|
9
|
+
s.date = %q{2009-07-25}
|
10
10
|
s.description = %q{Library for servers backing iPhone applications.}
|
11
11
|
s.email = %q{victor@zergling.net}
|
12
|
-
s.extra_rdoc_files = ["CHANGELOG", "lib/imobile/crypto_app_fprint.rb", "lib/imobile/validate_receipt.rb", "lib/imobile.rb", "LICENSE", "README"]
|
13
|
-
s.files = ["CHANGELOG", "imobile.
|
12
|
+
s.extra_rdoc_files = ["CHANGELOG", "lib/imobile/crypto_app_fprint.rb", "lib/imobile/push_notification.rb", "lib/imobile/validate_receipt.rb", "lib/imobile.rb", "LICENSE", "README"]
|
13
|
+
s.files = ["CHANGELOG", "lib/imobile/crypto_app_fprint.rb", "lib/imobile/push_notification.rb", "lib/imobile/validate_receipt.rb", "lib/imobile.rb", "LICENSE", "Manifest", "Rakefile", "README", "test/crypto_app_fprint_test.rb", "test/push_notification_test.rb", "test/validate_receipt_test.rb", "testdata/apns_developer.p12", "testdata/apns_production.p12", "testdata/device_attributes.yml", "testdata/encoded_notification", "testdata/forged_sandbox_receipt", "testdata/sandbox_device_token", "testdata/sandbox_device_token.bin", "testdata/valid_sandbox_receipt", "imobile.gemspec"]
|
14
14
|
s.homepage = %q{http://github.com/costan/imobile}
|
15
15
|
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Imobile", "--main", "README"]
|
16
16
|
s.require_paths = ["lib"]
|
17
17
|
s.rubyforge_project = %q{zerglings}
|
18
18
|
s.rubygems_version = %q{1.3.5}
|
19
19
|
s.summary = %q{Library for servers backing iPhone applications.}
|
20
|
-
s.test_files = ["test/crypto_app_fprint_test.rb", "test/validate_receipt_test.rb"]
|
20
|
+
s.test_files = ["test/crypto_app_fprint_test.rb", "test/push_notification_test.rb", "test/validate_receipt_test.rb"]
|
21
21
|
|
22
22
|
if s.respond_to? :specification_version then
|
23
23
|
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
@@ -0,0 +1,261 @@
|
|
1
|
+
# Apple Push Notifications support.
|
2
|
+
#
|
3
|
+
# Author:: Victor Costan
|
4
|
+
# Copyright:: Copyright (C) 2009 Zergling.Net
|
5
|
+
# License:: MIT
|
6
|
+
|
7
|
+
require 'net/http'
|
8
|
+
require 'net/https'
|
9
|
+
|
10
|
+
require 'socket'
|
11
|
+
require 'openssl'
|
12
|
+
|
13
|
+
require 'rubygems'
|
14
|
+
require 'json'
|
15
|
+
|
16
|
+
|
17
|
+
# :nodoc: namespace
|
18
|
+
module Imobile
|
19
|
+
|
20
|
+
# Sends a push notification to an iMobile device.
|
21
|
+
#
|
22
|
+
# Args:
|
23
|
+
# notification:: ruby Hash indicating the desired notification; the hash
|
24
|
+
# should have an extra key named :device_token, containing
|
25
|
+
# the binary-encoded (not hexadecimally-encoded) iMobile device
|
26
|
+
# token, as provided by the UIApplicationDelegate method
|
27
|
+
# application:didRegisterForRemoteNotificationsWithDeviceToken:
|
28
|
+
# path_or_certificate:: the certificate required to talk to APNs; this can be
|
29
|
+
# a path to a .p12 file, a string with the contens of
|
30
|
+
# the .p12 file, or a previously-read certificate
|
31
|
+
#
|
32
|
+
# Raises a RuntimeException if Apple's Push Notification service doesn't behave.
|
33
|
+
def self.push_notification(notification, path_or_certificate)
|
34
|
+
PushNotifications.push_notification notification, path_or_certificate
|
35
|
+
end
|
36
|
+
|
37
|
+
# Bulk-transmission of push notifications to Apple's service.
|
38
|
+
#
|
39
|
+
# Args:
|
40
|
+
# path_or_certificate:: see push_notification
|
41
|
+
# notifications:: an array of notification hashes; see push_notification
|
42
|
+
#
|
43
|
+
# If the method receives a block, it yields to its block indefinitely. Each
|
44
|
+
# time, the block should +next+ a notification or array of notifications to be
|
45
|
+
# pushed. The block should +break+ when it's done.
|
46
|
+
def self.push_notifications(path_or_certificate, notifications = [], &block)
|
47
|
+
PushNotifications.push_notifications path_or_certificate, notifications,
|
48
|
+
&block
|
49
|
+
end
|
50
|
+
|
51
|
+
# Reads the available feedback from Apple's Push Notification service.
|
52
|
+
#
|
53
|
+
# Args:
|
54
|
+
# certificate_or_path:: see Imobile.push_notification
|
55
|
+
#
|
56
|
+
# The currently provided feedback is the tokens for the devices which rejected
|
57
|
+
# notifications. Each piece of feedback is a hash with the following keys:
|
58
|
+
# :device_token:: the device's token, in binary (not hexadecimal) format
|
59
|
+
# :time:: the last time when the device rejected notifications; according to
|
60
|
+
# Apple, the rejection can be discarded if the device sent a
|
61
|
+
# token after this time
|
62
|
+
#
|
63
|
+
# The method reads all the feedback available from the Push Notification
|
64
|
+
# service. If a block is given, each piece of feedback is yielded to the
|
65
|
+
# method's block, and the method returns nil. If no block is given, the
|
66
|
+
# method returns an array containing all pieces of feedback.
|
67
|
+
def self.push_feedback(path_or_certificate, &block)
|
68
|
+
PushNotifications.push_feedback path_or_certificate, &block
|
69
|
+
end
|
70
|
+
|
71
|
+
# Checks if a notification is valid for Apple's Push Notification service.
|
72
|
+
#
|
73
|
+
# Currently, notifications are valid if their JSON encodings don't exceed 256
|
74
|
+
# bytes.
|
75
|
+
def self.valid_notification?(notification)
|
76
|
+
PushNotifications.encode_notification(notification) ? true : false
|
77
|
+
end
|
78
|
+
|
79
|
+
# Packs a hexadecimal iMobile device token into binary form.
|
80
|
+
def self.pack_hex_device_token(device_token)
|
81
|
+
[device_token.gsub(/\s/, '')].pack('H*')
|
82
|
+
end
|
83
|
+
|
84
|
+
# Implementation details for push_notification.
|
85
|
+
module PushNotifications
|
86
|
+
# Reads an APNs certificate from a string or a file.
|
87
|
+
def self.read_certificate(certificate_blob_or_path)
|
88
|
+
unless certificate_blob_or_path.respond_to? :to_str
|
89
|
+
return certificate_blob_or_path
|
90
|
+
end
|
91
|
+
begin
|
92
|
+
decode_push_certificate File.read(certificate_blob_or_path)
|
93
|
+
rescue
|
94
|
+
decode_push_certificate certificate_blob_or_path
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Decodes an APNs certificate.
|
99
|
+
def self.decode_push_certificate(certificate_blob)
|
100
|
+
pkcs12 = OpenSSL::PKCS12.new certificate_blob
|
101
|
+
|
102
|
+
certificate = pkcs12.certificate
|
103
|
+
key = pkcs12.key
|
104
|
+
case certificate.subject.to_s
|
105
|
+
when /Apple Development Push/
|
106
|
+
server_type = :sandbox
|
107
|
+
when /Apple Production Push/
|
108
|
+
server_type = :production
|
109
|
+
else
|
110
|
+
raise "Invalid push certificate - #{certificate.inspect}"
|
111
|
+
end
|
112
|
+
|
113
|
+
{ :certificate => certificate, :key => key, :server_type => server_type }
|
114
|
+
end
|
115
|
+
|
116
|
+
# Encodes a push notification in a binary string for APNs consumption.
|
117
|
+
#
|
118
|
+
# Returns a string suitable for transmission over an APNs, or nil if the
|
119
|
+
# notification is invalid (i.e. the json encoding exceeds 256 bytes).
|
120
|
+
def self.encode_notification(notification)
|
121
|
+
device_token = notification[:device_token] || ''
|
122
|
+
notification = notification.dup
|
123
|
+
notification.delete :device_token
|
124
|
+
json_notification = notification.to_json
|
125
|
+
return nil if json_notification.length > 256
|
126
|
+
|
127
|
+
["\0", [device_token.length].pack('n'), device_token,
|
128
|
+
[json_notification.length].pack('n'), json_notification].join
|
129
|
+
end
|
130
|
+
|
131
|
+
# Creates a socket to an Apple Push Notification Server.
|
132
|
+
#
|
133
|
+
# Args:
|
134
|
+
# push_certificate:: the APNs client certificate data, obtained by a call to
|
135
|
+
# read_certificate
|
136
|
+
# service:: either :feedback or :push
|
137
|
+
#
|
138
|
+
# The returned socket is connected and ready for use.
|
139
|
+
def self.apns_socket(push_certificate, service = :push)
|
140
|
+
context = OpenSSL::SSL::SSLContext.new
|
141
|
+
context.cert = push_certificate[:certificate]
|
142
|
+
context.key = push_certificate[:key]
|
143
|
+
|
144
|
+
server_type = push_certificate[:server_type]
|
145
|
+
raw_socket = TCPSocket.new apns_host(server_type, service),
|
146
|
+
apns_port(server_type, service)
|
147
|
+
|
148
|
+
socket = OpenSSL::SSL::SSLSocket.new raw_socket, context
|
149
|
+
# Magic for closing the raw socket when the SSL socket is closed.
|
150
|
+
(class <<socket; self; end).send :define_method, :close do
|
151
|
+
super
|
152
|
+
raw_socket.close
|
153
|
+
end
|
154
|
+
socket.sync = true
|
155
|
+
socket.connect
|
156
|
+
end
|
157
|
+
|
158
|
+
# The host name for an Apple Push Notification Server.
|
159
|
+
#
|
160
|
+
# Args:
|
161
|
+
# server_type:: either :production or :sandbox
|
162
|
+
# service:: either :push or :feedback
|
163
|
+
def self.apns_host(server_type, service = :push)
|
164
|
+
{
|
165
|
+
:feedback => {
|
166
|
+
:sandbox => 'feedback.sandbox.push.apple.com',
|
167
|
+
:production => 'feedback.push.apple.com'
|
168
|
+
},
|
169
|
+
:push => {
|
170
|
+
:sandbox => 'gateway.sandbox.push.apple.com',
|
171
|
+
:production => 'gateway.push.apple.com'
|
172
|
+
}
|
173
|
+
}[service][server_type]
|
174
|
+
end
|
175
|
+
|
176
|
+
# The port for an Apple Push Notification Server.
|
177
|
+
#
|
178
|
+
# Args:
|
179
|
+
# server_type:: either :production or :sandbox
|
180
|
+
# service:: either :push or :feedback
|
181
|
+
def self.apns_port(server_type, service = :push)
|
182
|
+
{
|
183
|
+
:feedback => 2196,
|
184
|
+
:push => 2195
|
185
|
+
}[service]
|
186
|
+
end
|
187
|
+
|
188
|
+
# Real implementation of Imobile.push_notifications
|
189
|
+
def self.push_notifications(certificate_or_path, notifications)
|
190
|
+
socket = apns_socket read_certificate(certificate_or_path), :push
|
191
|
+
notifications = [notifications] if notifications.kind_of? Hash
|
192
|
+
notifications.each { |n| socket.write encode_notification(n) }
|
193
|
+
if Kernel.block_given?
|
194
|
+
loop do
|
195
|
+
notifications = yield
|
196
|
+
notifications = [notifications] if notifications.kind_of? Hash
|
197
|
+
notifications.each { |n| socket.write encode_notification(n) }
|
198
|
+
end
|
199
|
+
end
|
200
|
+
socket.close
|
201
|
+
end
|
202
|
+
|
203
|
+
# Real implementation of Imobile.push_notification
|
204
|
+
def self.push_notification(notification, certificate_or_path)
|
205
|
+
push_notifications certificate_or_path, [notification]
|
206
|
+
end
|
207
|
+
|
208
|
+
# Real implementation of Imobile.push_feedback
|
209
|
+
def self.push_feedback(certificate_or_path, &block)
|
210
|
+
if Kernel.block_given?
|
211
|
+
raw_push_feedback certificate_or_path, &block
|
212
|
+
nil
|
213
|
+
else
|
214
|
+
feedback = []
|
215
|
+
raw_push_feedback certificate_or_path do |feedback_item|
|
216
|
+
feedback << feedback_item
|
217
|
+
end
|
218
|
+
feedback
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
# Reads the available feedback from Apple's Push Notification service.
|
223
|
+
#
|
224
|
+
# Args:
|
225
|
+
# certificate_or_path:: see Imobile.push_notification
|
226
|
+
#
|
227
|
+
# The currently provided feedback is the tokens for the devices which rejected
|
228
|
+
# notifications. Each piece of feedback is a hash with the following keys:
|
229
|
+
# :device_token:: the device's token, in binary (not hexadecimal) format
|
230
|
+
# :time:: the last time when the device rejected notifications; according to
|
231
|
+
# Apple, the rejection can be discarded if the device sent a
|
232
|
+
# token after this time
|
233
|
+
#
|
234
|
+
# The method reads all the feedback available from the Push Notification
|
235
|
+
# service, and yields each piece of feedback to the method's block.
|
236
|
+
def self.raw_push_feedback(certificate_or_path)
|
237
|
+
socket = apns_socket read_certificate(certificate_or_path), :feedback
|
238
|
+
loop do
|
239
|
+
break unless header = fixed_socket_read(socket, 6)
|
240
|
+
time = Time.at header[0, 4].unpack('N').first
|
241
|
+
device_token = fixed_socket_read(socket, header[4, 2].unpack('n').first)
|
242
|
+
break unless device_token
|
243
|
+
feedback_item = { :device_token => device_token, :time => time }
|
244
|
+
yield feedback_item
|
245
|
+
end
|
246
|
+
socket.close
|
247
|
+
end
|
248
|
+
|
249
|
+
# Reads a fixed number of bytes from a socket.
|
250
|
+
def self.fixed_socket_read(socket, num_bytes)
|
251
|
+
data = ''
|
252
|
+
while data.length < num_bytes
|
253
|
+
new_data = socket.read(num_bytes - data.length)
|
254
|
+
return nil if new_data.nil? or new_data.empty? # Socket closed.
|
255
|
+
data += new_data
|
256
|
+
end
|
257
|
+
data
|
258
|
+
end
|
259
|
+
end # module PushNotifications
|
260
|
+
|
261
|
+
end # namespace Imobile
|
data/lib/imobile.rb
CHANGED
@@ -0,0 +1,177 @@
|
|
1
|
+
# Author:: Victor Costan
|
2
|
+
# Copyright:: Copyright (C) 2009 Zergling.Net
|
3
|
+
# License:: MIT
|
4
|
+
|
5
|
+
require 'imobile'
|
6
|
+
|
7
|
+
require 'time'
|
8
|
+
require 'test/unit'
|
9
|
+
|
10
|
+
require 'rubygems'
|
11
|
+
require 'flexmock/test_unit'
|
12
|
+
|
13
|
+
|
14
|
+
class PushNotificationTest < Test::Unit::TestCase
|
15
|
+
def setup
|
16
|
+
testdata_path = File.join(File.dirname(__FILE__), '..', 'testdata')
|
17
|
+
@dev_cert_path = File.join(testdata_path, 'apns_developer.p12')
|
18
|
+
@prod_cert_path = File.join(testdata_path, 'apns_production.p12')
|
19
|
+
|
20
|
+
@hex_dev_token = File.read File.join(testdata_path, 'sandbox_device_token')
|
21
|
+
@dev_token = File.read File.join(testdata_path, 'sandbox_device_token.bin')
|
22
|
+
|
23
|
+
@notification = {:aps => {:alert => 'imobile test notification'}}
|
24
|
+
@encoded_notification = File.read File.join(testdata_path,
|
25
|
+
'encoded_notification')
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_read_push_certificate
|
29
|
+
cert_data = Imobile::PushNotifications.read_certificate @dev_cert_path
|
30
|
+
assert cert_data, "Dev certificate didn't load"
|
31
|
+
assert_equal :sandbox, cert_data[:server_type],
|
32
|
+
"Dev certificate mistaken for prod"
|
33
|
+
assert cert_data[:key].kind_of?(OpenSSL::PKey::PKey),
|
34
|
+
"Dev certificate does not contain a key"
|
35
|
+
assert(/Q686F7Z6YU/ =~ cert_data[:certificate].subject.to_s,
|
36
|
+
"Wrong data in dev certificate #{cert_data[:certificate].inspect}")
|
37
|
+
|
38
|
+
prod_blob = File.read @prod_cert_path
|
39
|
+
cert_data = Imobile::PushNotifications.read_certificate prod_blob
|
40
|
+
assert cert_data, "Prod certificate didn't load"
|
41
|
+
assert_equal :production, cert_data[:server_type],
|
42
|
+
"Prod certificate mistaken for dev"
|
43
|
+
assert cert_data[:key].kind_of?(OpenSSL::PKey::PKey),
|
44
|
+
"Prod certificate does not contain a key"
|
45
|
+
assert(/Q686F7Z6YU/ =~ cert_data[:certificate].subject.to_s,
|
46
|
+
"Wrong data in prod certificate #{cert_data[:certificate].inspect}")
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_pack_device_token
|
50
|
+
assert_equal @dev_token, Imobile.pack_hex_device_token(@hex_dev_token)
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_encode_notification
|
54
|
+
notification = @notification.merge :device_token => @dev_token
|
55
|
+
encoded = Imobile::PushNotifications.encode_notification notification
|
56
|
+
|
57
|
+
assert_equal @encoded_notification, encoded
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_valid_notification
|
61
|
+
assert Imobile.valid_notification?(@notification),
|
62
|
+
'Failed on easy valid notification'
|
63
|
+
notification = @notification.merge :device_token => '1' * 512
|
64
|
+
assert Imobile.valid_notification?(notification),
|
65
|
+
'Failed on notification with large device token'
|
66
|
+
notification[:aps][:alert] = '1' * 512
|
67
|
+
assert !Imobile.valid_notification?(notification),
|
68
|
+
'Passed notification with large alert'
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_apns_host_port
|
72
|
+
[
|
73
|
+
[:push, @dev_cert_path, 'gateway.sandbox.push.apple.com', 2195],
|
74
|
+
[:push, @prod_cert_path, 'gateway.push.apple.com', 2195],
|
75
|
+
[:feedback, @dev_cert_path, 'feedback.sandbox.push.apple.com', 2196],
|
76
|
+
[:feedback, @prod_cert_path, 'feedback.push.apple.com', 2196]
|
77
|
+
].each do |service, cert_path, gold_host, gold_port|
|
78
|
+
cert = Imobile::PushNotifications.read_certificate cert_path
|
79
|
+
server_type = cert[:server_type]
|
80
|
+
assert_equal gold_host,
|
81
|
+
Imobile::PushNotifications.apns_host(server_type, service)
|
82
|
+
assert_equal gold_port,
|
83
|
+
Imobile::PushNotifications.apns_port(server_type, service)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def test_smoke_push
|
88
|
+
notification = @notification.merge :device_token => @dev_token
|
89
|
+
Imobile.push_notification notification, @dev_cert_path
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_feedback_decoding
|
93
|
+
time1 = Time.at((Time.now - 3600).to_i)
|
94
|
+
time2 = Time.at((Time.now - 3600 * 72).to_i)
|
95
|
+
token1 = @dev_token
|
96
|
+
token2 = @dev_token.reverse
|
97
|
+
golden_feedback = [
|
98
|
+
{:device_token => token1, :time => time1},
|
99
|
+
{:device_token => token2, :time => time2}
|
100
|
+
]
|
101
|
+
|
102
|
+
reads = [
|
103
|
+
[6, [time1.to_i].pack('N')],
|
104
|
+
[2, [token1.length].pack('n')],
|
105
|
+
[token1.length, token1[0, 10]],
|
106
|
+
[token1.length - 10, token1[10, token1.length - 10]],
|
107
|
+
[6, [time2.to_i].pack('N')[0, 3]],
|
108
|
+
[3, [time2.to_i].pack('N')[3, 1] + [token2.length].pack('n')],
|
109
|
+
[token2.length, token2],
|
110
|
+
[6, '']
|
111
|
+
]
|
112
|
+
|
113
|
+
dev_cert = Imobile::PushNotifications.read_certificate @dev_cert_path
|
114
|
+
prod_cert = Imobile::PushNotifications.read_certificate @prod_cert_path
|
115
|
+
socket1, socket2 = MockSocket.new(reads), MockSocket.new(reads)
|
116
|
+
flexmock(Imobile::PushNotifications).should_receive(:apns_socket).
|
117
|
+
with(dev_cert, :feedback).and_return(socket1).once
|
118
|
+
flexmock(Imobile::PushNotifications).should_receive(:apns_socket).
|
119
|
+
with(prod_cert, :feedback).and_return(socket2)
|
120
|
+
|
121
|
+
# Test no-block codepath.
|
122
|
+
assert_equal golden_feedback, Imobile.push_feedback(dev_cert),
|
123
|
+
"No-block codepath failed."
|
124
|
+
assert socket1.closed, "No-block codepath didn't close socket"
|
125
|
+
|
126
|
+
# Test block codepath
|
127
|
+
output_feedback = []
|
128
|
+
Imobile.push_feedback prod_cert do |feedback_item|
|
129
|
+
output_feedback << feedback_item
|
130
|
+
end
|
131
|
+
assert_equal golden_feedback, output_feedback, "Block codepath failed."
|
132
|
+
assert socket2.closed, "Vlock codepath didn't close socket"
|
133
|
+
end
|
134
|
+
|
135
|
+
def test_smoke_feedback
|
136
|
+
Imobile.push_feedback @dev_cert_path do |feedback_item|
|
137
|
+
check_feedback_item feedback_item
|
138
|
+
end
|
139
|
+
|
140
|
+
feedback_items = Imobile.push_feedback @prod_cert_path
|
141
|
+
feedback_items.each { |feedback_item| check_feedback_item feedback_item }
|
142
|
+
end
|
143
|
+
|
144
|
+
# Verifies that a piece of feedback looks valid.
|
145
|
+
def check_feedback_item(feedback)
|
146
|
+
assert feedback[:device_token],
|
147
|
+
"A feedback item does not contain a device token"
|
148
|
+
assert feedback[:time],
|
149
|
+
"A feedback item does not contain a timestamp"
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
|
154
|
+
class MockSocket
|
155
|
+
def initialize(expected_reads)
|
156
|
+
@expected_reads = expected_reads.dup
|
157
|
+
@closed = false
|
158
|
+
end
|
159
|
+
attr_reader :closed
|
160
|
+
|
161
|
+
def read(bytes)
|
162
|
+
expected_read = @expected_reads.shift
|
163
|
+
unless expected_read
|
164
|
+
raise "Unexpected read of #{bytes.inspect} (done sending data)"
|
165
|
+
end
|
166
|
+
|
167
|
+
unless bytes == expected_read[0]
|
168
|
+
raise "Expected read size #{expected_read[0]}, got #{bytes.inspect}"
|
169
|
+
end
|
170
|
+
expected_read[1]
|
171
|
+
end
|
172
|
+
|
173
|
+
def close
|
174
|
+
raise "Closed prematurely" unless @expected_reads.empty?
|
175
|
+
@closed = true
|
176
|
+
end
|
177
|
+
end
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1 @@
|
|
1
|
+
af9788cb 35d74ed3 4ed94bfc 1fbd4ad6 cdc7b460 3c47a04a 98bf7162 ad1305c6
|
@@ -0,0 +1 @@
|
|
1
|
+
����5�N�N�K��J��Ǵ`<G�J��qb��
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: imobile
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Victor Costan
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-07-
|
12
|
+
date: 2009-07-25 00:00:00 -04:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -51,14 +51,15 @@ extensions: []
|
|
51
51
|
extra_rdoc_files:
|
52
52
|
- CHANGELOG
|
53
53
|
- lib/imobile/crypto_app_fprint.rb
|
54
|
+
- lib/imobile/push_notification.rb
|
54
55
|
- lib/imobile/validate_receipt.rb
|
55
56
|
- lib/imobile.rb
|
56
57
|
- LICENSE
|
57
58
|
- README
|
58
59
|
files:
|
59
60
|
- CHANGELOG
|
60
|
-
- imobile.gemspec
|
61
61
|
- lib/imobile/crypto_app_fprint.rb
|
62
|
+
- lib/imobile/push_notification.rb
|
62
63
|
- lib/imobile/validate_receipt.rb
|
63
64
|
- lib/imobile.rb
|
64
65
|
- LICENSE
|
@@ -66,10 +67,17 @@ files:
|
|
66
67
|
- Rakefile
|
67
68
|
- README
|
68
69
|
- test/crypto_app_fprint_test.rb
|
70
|
+
- test/push_notification_test.rb
|
69
71
|
- test/validate_receipt_test.rb
|
72
|
+
- testdata/apns_developer.p12
|
73
|
+
- testdata/apns_production.p12
|
70
74
|
- testdata/device_attributes.yml
|
75
|
+
- testdata/encoded_notification
|
71
76
|
- testdata/forged_sandbox_receipt
|
77
|
+
- testdata/sandbox_device_token
|
78
|
+
- testdata/sandbox_device_token.bin
|
72
79
|
- testdata/valid_sandbox_receipt
|
80
|
+
- imobile.gemspec
|
73
81
|
has_rdoc: true
|
74
82
|
homepage: http://github.com/costan/imobile
|
75
83
|
licenses: []
|
@@ -105,4 +113,5 @@ specification_version: 3
|
|
105
113
|
summary: Library for servers backing iPhone applications.
|
106
114
|
test_files:
|
107
115
|
- test/crypto_app_fprint_test.rb
|
116
|
+
- test/push_notification_test.rb
|
108
117
|
- test/validate_receipt_test.rb
|