passbook2 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 4a58374078ae35a90f7e9704ff3e643e6936dc64f8d218861f0e4e5fbc93edfb
4
+ data.tar.gz: 4d1ff0d20b11ee00f5b2062d83dc48979e7b5c1e9916ff401843709a7f309217
5
+ SHA512:
6
+ metadata.gz: dcc263f3738566ddc9eddc287330bde5908e4503755c71bbdd872679258e3c38b97681fd8e021fff443d243de143cddbc88eab56ba07014d4f58e77a6c23c9b5
7
+ data.tar.gz: f1f04770847c5e8c26a5ab2354d5dae23f3555e372dafc73eebb5baa317fd2ac87695bb042c4ad29ff04d1981e6630704cf4f91b7e985488dcc1fb976ebc93e6
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 1.9.2
5
+ - ruby-head
6
+ - 2.0.0
7
+ - 2.1.2
data/Gemfile ADDED
@@ -0,0 +1,18 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in passbook.gemspec
4
+ gem 'rubyzip', '>= 1.0.0'
5
+ gem 'grocer'
6
+ gem 'commander'
7
+ gem 'terminal-table'
8
+
9
+ group :test, :development do
10
+ gem 'rack-test'
11
+ gem 'activesupport'
12
+ gem 'jeweler'
13
+ gem 'simplecov'
14
+ gem 'rspec'
15
+ gem 'rake'
16
+ gem 'yard'
17
+ gem 'debug'
18
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,136 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ activesupport (7.0.4.3)
5
+ concurrent-ruby (~> 1.0, >= 1.0.2)
6
+ i18n (>= 1.6, < 2)
7
+ minitest (>= 5.1)
8
+ tzinfo (~> 2.0)
9
+ addressable (2.4.0)
10
+ builder (3.2.4)
11
+ commander (4.6.0)
12
+ highline (~> 2.0.0)
13
+ concurrent-ruby (1.2.2)
14
+ debug (1.8.0)
15
+ irb (>= 1.5.0)
16
+ reline (>= 0.3.1)
17
+ descendants_tracker (0.0.4)
18
+ thread_safe (~> 0.3, >= 0.3.1)
19
+ diff-lcs (1.5.0)
20
+ docile (1.4.0)
21
+ faraday (0.9.2)
22
+ multipart-post (>= 1.2, < 3)
23
+ git (1.11.0)
24
+ rchardet (~> 1.8)
25
+ github_api (0.16.0)
26
+ addressable (~> 2.4.0)
27
+ descendants_tracker (~> 0.0.4)
28
+ faraday (~> 0.8, < 0.10)
29
+ hashie (>= 3.4)
30
+ mime-types (>= 1.16, < 3.0)
31
+ oauth2 (~> 1.0)
32
+ grocer (0.7.1)
33
+ hashie (5.0.0)
34
+ highline (2.0.3)
35
+ i18n (1.13.0)
36
+ concurrent-ruby (~> 1.0)
37
+ io-console (0.6.0)
38
+ io-console (0.6.0-java)
39
+ irb (1.7.4)
40
+ reline (>= 0.3.6)
41
+ jar-dependencies (0.4.1)
42
+ jeweler (2.3.9)
43
+ builder
44
+ bundler
45
+ git (>= 1.2.5)
46
+ github_api (~> 0.16.0)
47
+ highline (>= 1.6.15)
48
+ nokogiri (>= 1.5.10)
49
+ psych
50
+ rake
51
+ rdoc
52
+ semver2
53
+ jwt (2.7.0)
54
+ mime-types (2.99.3)
55
+ mini_portile2 (2.8.1)
56
+ minitest (5.18.0)
57
+ multi_json (1.15.0)
58
+ multi_xml (0.6.0)
59
+ multipart-post (2.3.0)
60
+ nokogiri (1.14.3)
61
+ mini_portile2 (~> 2.8.0)
62
+ racc (~> 1.4)
63
+ nokogiri (1.14.3-java)
64
+ racc (~> 1.4)
65
+ oauth2 (1.4.8)
66
+ faraday (>= 0.8, < 3.0)
67
+ jwt (>= 1.0, < 3.0)
68
+ multi_json (~> 1.3)
69
+ multi_xml (~> 0.5)
70
+ rack (>= 1.2, < 3)
71
+ psych (5.1.0)
72
+ stringio
73
+ psych (5.1.0-java)
74
+ jar-dependencies (>= 0.1.7)
75
+ racc (1.6.2)
76
+ racc (1.6.2-java)
77
+ rack (2.2.7)
78
+ rack-test (2.1.0)
79
+ rack (>= 1.3)
80
+ rake (13.0.6)
81
+ rchardet (1.8.0)
82
+ rdoc (6.5.0)
83
+ psych (>= 4.0.0)
84
+ reline (0.3.8)
85
+ io-console (~> 0.5)
86
+ rspec (3.12.0)
87
+ rspec-core (~> 3.12.0)
88
+ rspec-expectations (~> 3.12.0)
89
+ rspec-mocks (~> 3.12.0)
90
+ rspec-core (3.12.2)
91
+ rspec-support (~> 3.12.0)
92
+ rspec-expectations (3.12.3)
93
+ diff-lcs (>= 1.2.0, < 2.0)
94
+ rspec-support (~> 3.12.0)
95
+ rspec-mocks (3.12.5)
96
+ diff-lcs (>= 1.2.0, < 2.0)
97
+ rspec-support (~> 3.12.0)
98
+ rspec-support (3.12.0)
99
+ rubyzip (2.3.2)
100
+ semver2 (3.4.2)
101
+ simplecov (0.22.0)
102
+ docile (~> 1.1)
103
+ simplecov-html (~> 0.11)
104
+ simplecov_json_formatter (~> 0.1)
105
+ simplecov-html (0.12.3)
106
+ simplecov_json_formatter (0.1.4)
107
+ stringio (3.0.6)
108
+ terminal-table (3.0.2)
109
+ unicode-display_width (>= 1.1.1, < 3)
110
+ thread_safe (0.3.6)
111
+ thread_safe (0.3.6-java)
112
+ tzinfo (2.0.6)
113
+ concurrent-ruby (~> 1.0)
114
+ unicode-display_width (2.4.2)
115
+ yard (0.9.34)
116
+
117
+ PLATFORMS
118
+ java
119
+ ruby
120
+
121
+ DEPENDENCIES
122
+ activesupport
123
+ commander
124
+ debug
125
+ grocer
126
+ jeweler
127
+ rack-test
128
+ rake
129
+ rspec
130
+ rubyzip (>= 1.0.0)
131
+ simplecov
132
+ terminal-table
133
+ yard
134
+
135
+ BUNDLED WITH
136
+ 2.4.10
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Thomas Lauro, Lance Gleason, 2024 Kay Rhodes
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,271 @@
1
+ # passbook2
2
+
3
+ The passbook2 gem let's you create a pkpass files for Apple's PassKit.
4
+ It is a fork of the "passbook" gem that has been updated to work with OpenSSL 3.0 and Ruby 3.x. It no longer uses p12 files which because they are no longer supported by Apple.
5
+
6
+ Apple's [PassKit](https://developer.apple.com/documentation/passkit_apple_pay_and_wallet) is used for processing Apple Pay payments & distributing tickets. This library is currently concerned with simplifying the process of generating a `.pass` bundle for distribution to your users. It's been thoroughly tested as a means of distributing even tickets to iOS devices.
7
+
8
+ This library does _not_ address updating information on tickets you've already distributed.
9
+
10
+ Note: This is a fork of [the original passbook gem](https://github.com/frozon/passbook). In addition to a lot of cleanup, it has been updated to support Ruby 3.0 and Apple's current cryptographic requirements.
11
+
12
+ ## Installation
13
+
14
+ Include the passbook2 gem in your project.
15
+
16
+ ```
17
+ gem 'passbook2'
18
+ ```
19
+
20
+ ## Configuration
21
+
22
+
23
+
24
+ ```ruby
25
+
26
+ # Note: the pb_confg variable below is typically a singleton
27
+ # configuration object that has knowledge of where you store
28
+ # your certificate files & related passwords
29
+ # You definitely should not be hardcoding that information.
30
+ pb_config = <your config object>
31
+
32
+ Passbook.configure do |passbook|
33
+
34
+ # Path to your wwdc cert file# path to the latest
35
+ # "Apple Intermediate Certificate Worldwide Developer Relations" certificate
36
+ # (.cer) file downloaded from here https://www.apple.com/certificateauthority/
37
+ # and placed somewhere under the rails root. It doesn't matter where.
38
+ # ⚠❗ these expire
39
+ passbook.apple_intermediate_cert = pb_config.apple_intermediate_certificate
40
+
41
+ # Path to your X509 cert. This is downloaded after generating
42
+ # a certificate from your apple Pass Type ID on apple's developer site
43
+ passbook.certificate = pb_config.x509_certificate
44
+
45
+ # Path to the .pem file generated from public key of the RSA keypair
46
+ # that was generated when you made a Certificate Signing Request
47
+ # It'll be in your keychain under the "Common Name" you specified
48
+ # for the signing request.
49
+ passbook.rsa_private_key = pb_config.private_key_pem
50
+
51
+ # Password for the .pem file above
52
+ passbook.password = pb_config.rsa_password
53
+ end
54
+
55
+ ```
56
+
57
+ Additional (optional) configuration variables:
58
+ - `notification_cert`
59
+ - `notification_gateway`
60
+ - `notification_passphrase`
61
+
62
+
63
+ ## Usage
64
+
65
+ First create a new `PKPass` object and pass it your pass' JSON data in the initializer. This will be stored in the pass' `pass.json` file.
66
+
67
+ ``` ruby
68
+ pass = Passbook::PKPass.new('{"YOUR STRINGIFIED JSON DATA"}')
69
+ ```
70
+
71
+ Then iterate over the files you need to add to the pass. The order you add them in doesn't matter, but the names must adhere to Apple's Passbook naming convention.
72
+
73
+ ``` ruby
74
+ pass.add_file("path/to/icon.png")
75
+ pass.add_files(["path/to/icon@2x.png", "path/to/thumbnail.png"])
76
+ ```
77
+
78
+ ![sample.pass](https://github.com/masukomi/passbook2/blob/master/docs/images/passbook_file_structure.png?raw=true)
79
+
80
+ Once you've added everything you need to your pass, it's time to generate the `.pkpass` zip file.
81
+
82
+ ``` ruby
83
+ pass.file # returns a Tempfile containing a ZipStream
84
+
85
+ # you can optionally specify the file name and directory to store it in.
86
+ pass.file({file_name: 'pass.pkpass', directory: Dir.tmpdir})
87
+
88
+ ```
89
+
90
+ In a Rails app you'd want to send the `.pkpass` data to users with the appropriate mime-type. Here's how to do that.
91
+
92
+ ``` ruby
93
+ zip_file_path = my_pass.file.path
94
+ File.open(zip_file_path, "r") do |file|
95
+ send_data(file.read,
96
+ type: "application/vnd.apple.pkpass",
97
+ disposition: "attachment",
98
+ filename: "#{thingy.descriptive_name}.pkpass")
99
+ end
100
+ ```
101
+
102
+ ### Creating MultiPasses
103
+
104
+ Apple supports the idea of a MultiPass. This is just a zipped collection of multiple passes. If, for example, someone buys 5 tickets, you can send them a MultiPass with all 5 wallet passes in it.
105
+
106
+ A MultiPass must contain between 1 and 10 passes (inclusive). If your user needs more than 10 items you'll just have to convince them to download multiple MultiPasses. This is annoying, but it's Apple's restriction so there's nothing we can do about it.
107
+
108
+ To create a MultiPass simply call `PKMultiPass.create_multipass` and give it an array of `PKPass` objects and a valid path and filename to store your MultiPass in. Note that it must end with `.pkpasses`
109
+
110
+ ``` ruby
111
+ my_file_path = File.join(temp_dir, "#{my_order_number}.pkpasses")
112
+ multipass_zip_file_path = Passbook::PKMultiPass.create_multipass(
113
+ array_of_pk_pass_objects, my_file_path
114
+ )
115
+
116
+ ```
117
+ This will return you the same path you passed in. The resulting `.pkpasses` zip file will use ordinal numbers for the names of the `.pkpass` files inside. E.g. `1.pkpass`, `2.pkpass`, `3.pkpass`, etc.
118
+
119
+ Note: if you know where the current documentation for creating MultiPass files is, please let me know.
120
+
121
+ Sending this data to users is exactly the same as sending the data for an individual pass.
122
+
123
+ -----
124
+
125
+ ### Original Passbook(1) functionality
126
+ ⚠ WARNING
127
+
128
+ The following features & documentation come from the original passbook gem, and have _not_ been tested with Apple's current requirements. Signing _does_ work by default, but custom signing has not been tested and appears to still be thinking in terms of `.p12` files which Apple no longer honors.
129
+
130
+ Please make a PR if you make an updated version of any of this.
131
+
132
+
133
+ #### Using Different Certificates For Different Passes
134
+
135
+ Sometime you might want to be able to use different certificates for different passes. This can be done by passing in a Signer class into your PKPass initializer. You don't have to use environment variables, but it's a good way to make these things easy to rotate in the future when the certs expire.
136
+
137
+ ```
138
+ signer = Passbook::Signer.new(
139
+ certificate: Rails.root.join(ENV['PASSBOOK_X509_CERTIFICATE']),
140
+ rsa_private_key: Rails.root.join(ENV['PASSBOOK_PRIVATE_KEY_PEM']),
141
+ password: ENV['PASSBOOK_RSA_PASSWORD'],
142
+ apple_intermediate_cert: Rails.root.join(ENV['PASSBOOK_APPLE_INTERMEDIATE_CERTIFICATE'])
143
+ )
144
+ pk_pass = Passbook::PKPass.new(data, signer)
145
+
146
+ ....
147
+ ```
148
+
149
+ ### Push Notifications
150
+
151
+ If you want to support passbook push notification updates you will need to configure the appropriate bits above.
152
+
153
+ In order to support push notifications you will need to have a basic understanding of the way that push notifications work and how the data is passed back and forth.
154
+
155
+ Your pass will need to have a field called 'webServiceURL' with the base url to your site and a field called 'authenticationToken'. The json snippet should look like this. Note that your url needs to be a valid signed https endpoint for production. You can put your phone in dev mode to test updates against a insecure http endpoint (under settings => developer => passkit testing).
156
+
157
+ ```
158
+ ...
159
+ "webserviceURL" : "https://www.honeybadgers.com/",
160
+ "authenticationToken" : "yummycobras"
161
+ ...
162
+ ```
163
+
164
+ Passbook includes rack middleware to make the job of supporting the passbook endpoints easier. You will need to configure the middleware as outlined above and then implement a class called Passbook::PassbookNotification. Below is an annotated implementation.
165
+
166
+ ```
167
+ module Passbook
168
+ class PassbookNotification
169
+
170
+ # This is called whenever a new pass is saved to a users passbook or the
171
+ # notifications are re-enabled. You will want to persist these values to
172
+ # allow for updates on subsequent calls in the call chain. You can have
173
+ # multiple push tokens and serial numbers for a specific
174
+ # deviceLibraryIdentifier.
175
+
176
+ def self.register_pass(options)
177
+ the_passes_serial_number = options['serialNumber']
178
+ the_devices_device_library_identifier = options['deviceLibraryIdentifier']
179
+ the_devices_push_token = options['pushToken']
180
+ the_pass_type_identifier = options["passTypeIdentifier"]
181
+ the_authentication_token = options['authToken']
182
+
183
+ # this is if the pass registered successfully
184
+ # change the code to 200 if the pass has already been registered
185
+ # 404 if pass not found for serialNubmer and passTypeIdentifier
186
+ # 401 if authorization failed
187
+ # or another appropriate code if something went wrong.
188
+ {:status => 201}
189
+ end
190
+
191
+ # This is called when the device receives a push notification from apple.
192
+ # You will need to return the serial number of all passes associated with
193
+ # that deviceLibraryIdentifier.
194
+
195
+ def self.passes_for_device(options)
196
+ device_library_identifier = options['deviceLibraryIdentifier']
197
+ passes_updated_since = options['passesUpdatedSince']
198
+
199
+ # the 'lastUpdated' uses integers values to tell passbook if the pass is
200
+ # more recent than the current one. If you just set it is the same value
201
+ # every time the pass will update and you will get a warning in the log files.
202
+ # you can use the time in milliseconds, a counter or any other numbering scheme.
203
+ # you then also need to return an array of serial numbers.
204
+ {'lastUpdated' => '1', 'serialNumbers' => ['various', 'serial', 'numbers']}
205
+ end
206
+
207
+ # this is called when a pass is deleted or the user selects the option to disable pass updates.
208
+ def self.unregister_pass(options)
209
+ # a solid unique pair of identifiers to identify the pass are
210
+ serial_number = options['serialNumber']
211
+ device_library_identifier = options['deviceLibraryIdentifier']
212
+ the_pass_type_identifier = options["passTypeIdentifier"]
213
+ the_authentication_token = options['authToken']
214
+ # return a status 200 to indicate that the pass was successfully unregistered.
215
+ {:status => 200}
216
+ end
217
+
218
+ # this returns your updated pass
219
+ def self.latest_pass(options)
220
+ the_pass_serial_number = options['serialNumber']
221
+ # create your PkPass the way you did when your first created the pass.
222
+ # you will want to return
223
+ my_pass = PkPass.new 'your pass json'
224
+ # you will want to return the string from the stream of your PkPass object.
225
+ {:status => 200, :latest_pass => mypass.stream.string, :last_modified => '1442120893'}
226
+ end
227
+
228
+ # This is called whenever there is something from the update process that is a warning
229
+ # or error
230
+ def self.passbook_log(log)
231
+ # this is a VERY crude logging example. use the logger of your choice here.
232
+ p "#{Time.now} #{log}"
233
+ end
234
+
235
+ end
236
+ end
237
+
238
+ ```
239
+
240
+ To send a push notification for a updated pass simply call Passbook::PushNotification.send_notification with the push token for the device you are updating
241
+
242
+ ```
243
+ Passbook::PushNotification.send_notification the_device_push_token
244
+
245
+ ```
246
+
247
+ Apple will send out a notification to your phone (usually within 15 minutes or less), which will cause the phone that this push notification is associated with to make a call to your server to get pass serial numbers and to then get the updated pass. Each phone/pass combination has it's own push token whch will require a separate call for every phone that has push notifications enabled for a pass (this is an Apple thing). In the future we may look into offering background process support for this as part of this gem. For now, if you have a lot of passes to update you will need to do this yourself.
248
+
249
+ ## Tests
250
+
251
+ To launch tests:
252
+
253
+ ```bash
254
+ bundle exec rake spec
255
+ ```
256
+
257
+ ## Contributing
258
+
259
+ 1. Fork it
260
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
261
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
262
+ 4. Push to the branch (`git push origin my-new-feature`)
263
+ 5. Create new Pull Request
264
+
265
+
266
+ License
267
+ -------
268
+
269
+ passbook is released under the MIT license:
270
+
271
+ * http://www.opensource.org/licenses/MIT
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: %i[spec]
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.5.1
@@ -0,0 +1,26 @@
1
+ require 'zip'
2
+ require 'fileutils'
3
+ module Passbook
4
+ class PKMultiPass
5
+ def self.create_multipass(pk_passes, multipass_file_path)
6
+ raise "Too many passes" if pk_passes.size > 10
7
+ raise "No passes provided" if pk_passes.size < 1
8
+ raise "multipass files must have the .pkpasses extension" \
9
+ unless multipass_file_path.end_with? ".pkpasses"
10
+ raise "passes must be PKPass objects" \
11
+ unless pk_passes.all? { |pass| pass.is_a? Passbook::PKPass }
12
+ temp_dir = Dir.mktmpdir
13
+ pass_counter = 0
14
+ Zip::File.open(multipass_file_path, create: true) do |zipfile|
15
+ pk_passes.each do | pass |
16
+ pass_counter += 1
17
+ file_name = "#{pass_counter}.pkpass"
18
+ pkpass_path = pass.file(file_name: file_name, directory: temp_dir)
19
+ zipfile.add(file_name, pkpass_path)
20
+ end
21
+ end
22
+ FileUtils.remove_dir(temp_dir, true)
23
+ multipass_file_path
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,133 @@
1
+ require 'digest/sha1'
2
+ require 'openssl'
3
+ require 'zip'
4
+ require 'base64'
5
+
6
+ module Passbook
7
+ class PKPass
8
+ attr_accessor :pass, :manifest_files, :signer
9
+
10
+ TYPES = %w(boarding-pass coupon event-ticket store-card generic)
11
+
12
+ # Initializes a new Passbook::PKPass object
13
+ # @param pass [String] JSON string representing the pass
14
+ # @param init_signer [Passbook::Signer] (optional) a signer object
15
+ def initialize(pass, init_signer = nil)
16
+ @pass = pass
17
+ @manifest_files = []
18
+ @signer = init_signer || Passbook::Signer.new
19
+ end
20
+
21
+ # Adds a single file to the pass
22
+ def add_file(file)
23
+ @manifest_files << file
24
+ end
25
+
26
+ # Adds multiple files to the pass
27
+ def add_files(files_array)
28
+ @manifest_files += files_array
29
+ end
30
+
31
+ # List of files in the pass
32
+ def list_files
33
+ @manifest_files
34
+ end
35
+
36
+
37
+
38
+ # Return a Tempfile containing our ZipStream
39
+ # @param options [Hash] :file_name, :directory
40
+ # - file_name defaults to 'pass.pkpass'
41
+ # a tempfile will be created and renamed to this
42
+ # - directory defaults to Dir.tmpdir
43
+ # and can be specifed as an absolute string path or a
44
+ # Dir object
45
+ def file(options = {})
46
+ options[:file_name] ||= 'pass.pkpass'
47
+ options[:directory] ||= Dir.tmpdir
48
+ desired_path = File.join(options[:directory], options[:file_name])
49
+
50
+ File.binwrite(desired_path, self.stream.string)
51
+ File.new(desired_path)
52
+ end
53
+
54
+ # Return a ZipOutputStream
55
+ def stream
56
+ manifest, signature = build
57
+
58
+ output_zip manifest, signature
59
+ end
60
+
61
+ # Builds the pass.
62
+ # Creates the manifest, checks for necessary files and fields
63
+ # Returns an array with the manifest and the pass' cryptographic signature
64
+ # Don't call this unless you have a specific need. Use #file or #stream instead
65
+ def build
66
+ manifest = create_manifest
67
+
68
+ # Check pass for necessary files and fields
69
+ check_pass manifest
70
+
71
+ # Create pass signature
72
+ signature = @signer.sign manifest
73
+
74
+ [manifest, signature]
75
+ end
76
+
77
+ ## PRIVATE METHODS ##############################################################
78
+ private
79
+
80
+ def check_pass(manifest)
81
+ # Check for default images
82
+ raise 'Icon missing' unless manifest.include?('icon.png')
83
+ raise 'Icon@2x missing' unless manifest.include?('icon@2x.png')
84
+
85
+ # Check for developer field in JSON
86
+ raise 'Pass Type Identifier missing' unless @pass.include?('passTypeIdentifier')
87
+ raise 'Team Identifier missing' unless @pass.include?('teamIdentifier')
88
+ raise 'Serial Number missing' unless @pass.include?('serialNumber')
89
+ raise 'Organization Name Identifier missing' unless @pass.include?('organizationName')
90
+ raise 'Format Version' unless @pass.include?('formatVersion')
91
+ raise 'Format Version should be a numeric' unless JSON.parse(@pass)['formatVersion'].is_a?(Numeric)
92
+ raise 'Description' unless @pass.include?('description')
93
+ end
94
+
95
+ def create_manifest
96
+ sha1s = {}
97
+ sha1s['pass.json'] = Digest::SHA1.hexdigest @pass
98
+
99
+ @manifest_files.each do |file|
100
+ if file.class == Hash
101
+ sha1s[file[:name]] = Digest::SHA1.hexdigest file[:content]
102
+ else
103
+ # either a File or a Pathname
104
+ sha1s[File.basename(file)] = Digest::SHA1.file(File.absolute_path(file)).hexdigest
105
+ end
106
+ end
107
+
108
+ sha1s.to_json
109
+ end
110
+
111
+ def output_zip(manifest, signature)
112
+
113
+ Zip::OutputStream.write_buffer do |zip|
114
+ zip.put_next_entry 'pass.json'
115
+ zip.write @pass
116
+ zip.put_next_entry 'manifest.json'
117
+ zip.write manifest
118
+ zip.put_next_entry 'signature'
119
+ zip.write signature
120
+
121
+ @manifest_files.each do |file|
122
+ if file.class == Hash
123
+ zip.put_next_entry file[:name]
124
+ zip.print file[:content]
125
+ else
126
+ zip.put_next_entry File.basename(file)
127
+ zip.print IO.read(file)
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,19 @@
1
+ module Passbook
2
+ class PushNotification
3
+ class << self
4
+ def pusher
5
+ @pusher ||= Grocer.pusher(
6
+ :certificate => Passbook.notification_cert,
7
+ :passphrase => Passbook.notification_passphrase || "",
8
+ :gateway => Passbook.notification_gateway
9
+ )
10
+ end
11
+
12
+ def send_notification(device_token)
13
+ notification = Grocer::PassbookNotification.new(:device_token => device_token)
14
+
15
+ pusher.push notification
16
+ end
17
+ end
18
+ end
19
+ end