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 CHANGED
@@ -1,3 +1,5 @@
1
- v0.0.2. CryptoSupport fingerprints computation.
1
+ v0.0.3. Push Notification support.
2
+
3
+ v0.0.2. CryptoSupport finger-prints computation.
2
4
 
3
5
  v0.0.1. Initial release. In-App Purchase receipt validation.
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.2"
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-24}
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.gemspec", "lib/imobile/crypto_app_fprint.rb", "lib/imobile/validate_receipt.rb", "lib/imobile.rb", "LICENSE", "Manifest", "Rakefile", "README", "test/crypto_app_fprint_test.rb", "test/validate_receipt_test.rb", "testdata/device_attributes.yml", "testdata/forged_sandbox_receipt", "testdata/valid_sandbox_receipt"]
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
@@ -5,4 +5,5 @@
5
5
  # License:: MIT
6
6
 
7
7
  require 'imobile/crypto_app_fprint.rb'
8
+ require 'imobile/push_notification.rb'
8
9
  require 'imobile/validate_receipt.rb'
@@ -115,6 +115,6 @@ module CryptoSupportAppFprint
115
115
  manifest_data = File.read binary_path
116
116
  app_fprint_from_raw_data device_attributes, manifest_data
117
117
  end
118
- end
118
+ end # module CryptoSupportAppFprint
119
119
 
120
120
  end # namespace Imobile
@@ -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
@@ -99,6 +99,6 @@ module AppStoreReceiptValidation
99
99
 
100
100
  process_response issue_request(request(receipt_blob, uri), uri)
101
101
  end
102
- end
102
+ end # module AppStoreReceiptValidation
103
103
 
104
104
  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.2
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-24 00:00:00 -07:00
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