costan-imobile 0.0.2 → 0.0.3
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/CHANGELOG +3 -1
- data/Manifest +7 -1
- data/imobile.gemspec +5 -5
- data/lib/imobile.rb +1 -0
- 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/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
|
data/lib/imobile.rb
CHANGED
@@ -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
|
@@ -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: costan-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 -07: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: false
|
74
82
|
homepage: http://github.com/costan/imobile
|
75
83
|
post_install_message:
|
@@ -103,4 +111,5 @@ specification_version: 3
|
|
103
111
|
summary: Library for servers backing iPhone applications.
|
104
112
|
test_files:
|
105
113
|
- test/crypto_app_fprint_test.rb
|
114
|
+
- test/push_notification_test.rb
|
106
115
|
- test/validate_receipt_test.rb
|