passkit 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.example.env +6 -0
- data/README.md +124 -5
- data/app/assets/images/passkit/add_to_apple_wallet_de.png +0 -0
- data/app/assets/images/passkit/add_to_apple_wallet_en.png +0 -0
- data/app/assets/images/passkit/add_to_apple_wallet_fr.png +0 -0
- data/app/assets/images/passkit/add_to_apple_wallet_it.png +0 -0
- data/app/assets/images/passkit/add_to_walletpass_de.png +0 -0
- data/app/assets/images/passkit/add_to_walletpass_en.png +0 -0
- data/app/assets/images/passkit/add_to_walletpass_fr.png +0 -0
- data/app/assets/images/passkit/add_to_walletpass_it.png +0 -0
- data/app/controllers/passkit/api/v1/logs_controller.rb +14 -0
- data/app/controllers/passkit/api/v1/passes_controller.rb +58 -0
- data/app/controllers/passkit/api/v1/registrations_controller.rb +115 -0
- data/app/controllers/passkit/logs_controller.rb +9 -0
- data/app/controllers/passkit/passes_controller.rb +4 -19
- data/app/controllers/passkit/previews_controller.rb +13 -0
- data/app/mailers/passkit/example_mailer.rb +8 -0
- data/{lib → app/models}/passkit/device.rb +2 -2
- data/{lib → app/models}/passkit/log.rb +0 -0
- data/app/models/passkit/pass.rb +49 -0
- data/app/models/passkit/registration.rb +6 -0
- data/app/views/layouts/passkit/application.html.erb +16 -0
- data/app/views/passkit/example_mailer/example_email.html.erb +15 -0
- data/app/views/passkit/logs/index.html.erb +22 -0
- data/app/views/passkit/passes/index.html.erb +32 -0
- data/app/views/passkit/previews/index.html.erb +21 -0
- data/app/views/shared/passkit/_navigation.html.erb +5 -0
- data/config/routes.rb +17 -7
- data/docs/membership.png +0 -0
- data/docs/passkit_environment_variables.md +48 -0
- data/docs/step1.png +0 -0
- data/docs/step2.png +0 -0
- data/docs/step3.png +0 -0
- data/docs/step4.png +0 -0
- data/docs/step5.png +0 -0
- data/docs/wallet.png +0 -0
- data/lib/generators/passkit/install_generator.rb +26 -0
- data/lib/generators/templates/create_passkit_tables.rb.tt +32 -0
- data/lib/generators/templates/passkit.rb +3 -0
- data/lib/passkit/base_pass.rb +124 -0
- data/lib/passkit/example_store_card/icon.png +0 -0
- data/lib/passkit/example_store_card/icon@2x.png +0 -0
- data/lib/passkit/example_store_card/icon@3x.png +0 -0
- data/lib/passkit/example_store_card/logo.png +0 -0
- data/lib/passkit/example_store_card.rb +110 -0
- data/lib/passkit/factory.rb +2 -2
- data/lib/passkit/generator.rb +30 -22
- data/lib/passkit/payload_generator.rb +18 -0
- data/lib/passkit/url_encrypt.rb +3 -3
- data/lib/passkit/url_generator.rb +20 -0
- data/lib/passkit/version.rb +1 -1
- data/lib/passkit.rb +37 -3
- data/passkit-0.2.0.gem +0 -0
- data/sig/lib/passkit/encryptable_payload.rbs +5 -0
- data/sig/lib/passkit/factory.rbs +5 -0
- data/sig/lib/passkit/generator.rbs +8 -0
- data/sig/lib/passkit/generator_object.rbs +5 -0
- data/sig/lib/passkit/pass.rbs +5 -0
- data/sig/lib/passkit/url_encrypt.rbs +6 -0
- data/sig/lib/passkit/url_generator.rbs +9 -0
- metadata +140 -8
- data/lib/passkit/pass.rb +0 -8
- data/sig/passkit.rbs +0 -4
data/config/routes.rb
CHANGED
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
Passkit::Engine.routes.draw do
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
scope :api, constraints: {pass_type_id: /.*/} do
|
|
5
|
+
scope :v1 do
|
|
6
|
+
resources :devices, only: [] do
|
|
7
|
+
post "registrations/:pass_type_id/:serial_number" => "api/v1/registrations#create", :as => :register
|
|
8
|
+
delete "registrations/:pass_type_id/:serial_number" => "api/v1/registrations#destroy", :as => :unregister
|
|
9
|
+
get "registrations/:pass_type_id" => "api/v1/registrations#show", :as => :registrations
|
|
10
|
+
end
|
|
11
|
+
get "passes/:pass_type_id/:serial_number" => "api/v1/passes#show", :as => :pass
|
|
12
|
+
get "passes/:payload", to: "api/v1/passes#create", as: :passes_api
|
|
13
|
+
post "log" => "api/v1/logs#create", :as => :log
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
unless Rails.env.production?
|
|
18
|
+
resources :previews, only: [:index, :show], param: :class_name
|
|
19
|
+
resources :logs, only: [:index]
|
|
20
|
+
resources :passes, only: [:index]
|
|
8
21
|
end
|
|
9
|
-
get 'passes/:pass_type_id/:serial_number' => 'passes#show', as: :pass
|
|
10
|
-
get 'passes/:payload', to: 'passes#create', as: :passes
|
|
11
|
-
post 'log' => 'logs#create'
|
|
12
22
|
end
|
data/docs/membership.png
ADDED
|
Binary file
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Setup Passkit Environment variables
|
|
2
|
+
|
|
3
|
+
## `PASSKIT_WEB_SERVICE_HOST`
|
|
4
|
+
|
|
5
|
+
This is the host where your Rails app is running. It is used to generate the URLs for the passes.
|
|
6
|
+
When the device wants to update the Pass, it will invoke services on this host.
|
|
7
|
+
In production, it will simply be your domain name, but in development you can use [ngrok](https://ngrok.com/) to expose your local server to the internet.
|
|
8
|
+
|
|
9
|
+
**Remember that it must always start with `https://`.**
|
|
10
|
+
|
|
11
|
+
## `PASSKIT_APPLE_INTERMEDIATE_CERTIFICATE`
|
|
12
|
+
|
|
13
|
+
This is the easy one.
|
|
14
|
+
Head to https://www.apple.com/certificateauthority/ and download the latest Apple Intermediate Certificate Worldwide Developer Relations.
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
## `PASSKIT_APPLE_TEAM_IDENTIFIER`
|
|
18
|
+
|
|
19
|
+
You find this in your Apple Developer dashboard, under Membership.
|
|
20
|
+
|
|
21
|
+

|
|
22
|
+
|
|
23
|
+
## `PASSKIT_PASS_TYPE_IDENTIFIER`, `PASSKIT_PRIVATE_P12_CERTIFICATE` and `PASSKIT_CERTIFICATE_KEY`
|
|
24
|
+
|
|
25
|
+
Head to your Apple Developers console and generate a new certificate.
|
|
26
|
+
|
|
27
|
+

|
|
28
|
+
|
|
29
|
+

|
|
30
|
+
|
|
31
|
+

|
|
32
|
+
|
|
33
|
+
The identifier is the `PASSKIT_PASS_TYPE_IDENTIFIER` variable.
|
|
34
|
+
|
|
35
|
+
Now, create a certificate signing request: https://help.apple.com/developer-account/#/devbfa00fef7
|
|
36
|
+
|
|
37
|
+
And create the certificate:
|
|
38
|
+
|
|
39
|
+

|
|
40
|
+
|
|
41
|
+
At the end, you'll have a `pass.cer` file.
|
|
42
|
+
|
|
43
|
+
Open it in the Keychain Access tool and export it:
|
|
44
|
+
|
|
45
|
+

|
|
46
|
+
|
|
47
|
+
Set a password and get your p12 file. The path to this file is the `PASSKIT_PRIVATE_P12_CERTIFICATE` variable.
|
|
48
|
+
Save the password. This is the `PASSKIT_CERTIFICATE_KEY` variable.
|
data/docs/step1.png
ADDED
|
Binary file
|
data/docs/step2.png
ADDED
|
Binary file
|
data/docs/step3.png
ADDED
|
Binary file
|
data/docs/step4.png
ADDED
|
Binary file
|
data/docs/step5.png
ADDED
|
Binary file
|
data/docs/wallet.png
ADDED
|
Binary file
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators/base"
|
|
4
|
+
require "rails/generators/migration"
|
|
5
|
+
|
|
6
|
+
module Passkit
|
|
7
|
+
module Generators
|
|
8
|
+
class InstallGenerator < Rails::Generators::Base
|
|
9
|
+
include Rails::Generators::Migration
|
|
10
|
+
|
|
11
|
+
source_root File.expand_path("../../templates", __FILE__)
|
|
12
|
+
|
|
13
|
+
# Implement the required interface for Rails::Generators::Migration.
|
|
14
|
+
def self.next_migration_number(dirname)
|
|
15
|
+
next_migration_number = current_migration_number(dirname) + 1
|
|
16
|
+
ActiveRecord::Migration.next_migration_number(next_migration_number)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
desc "Copy all files to your application."
|
|
20
|
+
def generate_files
|
|
21
|
+
migration_template "create_passkit_tables.rb", "db/migrate/create_passkit_tables.rb"
|
|
22
|
+
copy_file "passkit.rb", "config/initializers/passkit.rb"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
class CreatePasskitTables < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
|
|
2
|
+
def change
|
|
3
|
+
create_table :passkit_passes do |t|
|
|
4
|
+
t.string :generator_type
|
|
5
|
+
t.string :klass
|
|
6
|
+
t.bigint :generator_id
|
|
7
|
+
t.string :serial_number
|
|
8
|
+
t.string :authentication_token
|
|
9
|
+
t.json :data
|
|
10
|
+
t.integer :version
|
|
11
|
+
t.timestamps null: false
|
|
12
|
+
t.index [:generator_type, :generator_id], name: 'index_passkit_passes_on_generator'
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
create_table :passkit_devices do |t|
|
|
16
|
+
t.string :identifier
|
|
17
|
+
t.string :push_token
|
|
18
|
+
t.timestamps null: false
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
create_table :passkit_registrations do |t|
|
|
22
|
+
t.belongs_to :passkit_pass, index: true
|
|
23
|
+
t.belongs_to :passkit_device, index: true
|
|
24
|
+
t.timestamps null: false
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
create_table :passkit_logs do |t|
|
|
28
|
+
t.text :content
|
|
29
|
+
t.timestamps null: false
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
module Passkit
|
|
2
|
+
class BasePass
|
|
3
|
+
def initialize(generator = nil)
|
|
4
|
+
@generator = generator
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def format_version
|
|
8
|
+
ENV["PASSKIT_FORMAT_VERSION"] || 1
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def apple_team_identifier
|
|
12
|
+
ENV["PASSKIT_APPLE_TEAM_IDENTIFIER"] || raise(Error.new("Missing environment variable: PASSKIT_APPLE_TEAM_IDENTIFIER"))
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def pass_type_identifier
|
|
16
|
+
ENV["PASSKIT_PASS_TYPE_IDENTIFIER"] || raise(Error.new("Missing environment variable: PASSKIT_PASS_TYPE_IDENTIFIER"))
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def language
|
|
20
|
+
nil
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def last_update
|
|
24
|
+
@generator&.updated_at
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def pass_path
|
|
28
|
+
rails_folder = Rails.root.join("private/passkit/#{folder_name}")
|
|
29
|
+
# if folder exists, otherwise is in the gem itself under lib/passkit/base_pass
|
|
30
|
+
if File.directory?(rails_folder)
|
|
31
|
+
rails_folder
|
|
32
|
+
else
|
|
33
|
+
File.join(File.dirname(__FILE__), folder_name)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def pass_type
|
|
38
|
+
:storeCard
|
|
39
|
+
# :coupon
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def web_service_url
|
|
43
|
+
raise Error.new("Missing environment variable: PASSKIT_WEB_SERVICE_HOST") unless ENV["PASSKIT_WEB_SERVICE_HOST"]
|
|
44
|
+
"#{ENV["PASSKIT_WEB_SERVICE_HOST"]}/passkit/api"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def foreground_color
|
|
48
|
+
"rgb(0, 0, 0)"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def background_color
|
|
52
|
+
"rgb(255, 255, 255)"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def organization_name
|
|
56
|
+
"Passkit"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def description
|
|
60
|
+
"A basic description for a pass"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# A pass can have up to ten relevant locations
|
|
64
|
+
#
|
|
65
|
+
# @see https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/PassKit_PG/Creating.html
|
|
66
|
+
def locations
|
|
67
|
+
[]
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def voided
|
|
71
|
+
false
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def file_name
|
|
75
|
+
@file_name ||= SecureRandom.uuid
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# QRCode by default
|
|
79
|
+
def barcode
|
|
80
|
+
{messageEncoding: "iso-8859-1",
|
|
81
|
+
format: "PKBarcodeFormatQR",
|
|
82
|
+
message: "https://github.com/coorasse/passkit",
|
|
83
|
+
altText: "https://github.com/coorasse/passkit"}
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Barcode example
|
|
87
|
+
# def barcode
|
|
88
|
+
# { messageEncoding: 'iso-8859-1',
|
|
89
|
+
# format: 'PKBarcodeFormatCode128',
|
|
90
|
+
# message: '12345',
|
|
91
|
+
# altText: '12345' }
|
|
92
|
+
# end
|
|
93
|
+
|
|
94
|
+
def logo_text
|
|
95
|
+
"Logo text"
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def header_fields
|
|
99
|
+
[]
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def primary_fields
|
|
103
|
+
[]
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def secondary_fields
|
|
107
|
+
[]
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def auxiliary_fields
|
|
111
|
+
[]
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def back_fields
|
|
115
|
+
[]
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
private
|
|
119
|
+
|
|
120
|
+
def folder_name
|
|
121
|
+
self.class.name.demodulize.underscore
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
module Passkit
|
|
2
|
+
class ExampleStoreCard < BasePass
|
|
3
|
+
def pass_type
|
|
4
|
+
:storeCard
|
|
5
|
+
# :coupon
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def foreground_color
|
|
9
|
+
"rgb(0, 0, 0)"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def background_color
|
|
13
|
+
"rgb(255, 255, 255)"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def organization_name
|
|
17
|
+
"Passkit"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def description
|
|
21
|
+
"A basic description for a pass"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# A pass can have up to ten relevant locations
|
|
25
|
+
#
|
|
26
|
+
# @see https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/PassKit_PG/Creating.html
|
|
27
|
+
def locations
|
|
28
|
+
[]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def voided
|
|
32
|
+
false
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def file_name
|
|
36
|
+
@file_name ||= SecureRandom.uuid
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# QRCode by default
|
|
40
|
+
def barcode
|
|
41
|
+
{messageEncoding: "iso-8859-1",
|
|
42
|
+
format: "PKBarcodeFormatQR",
|
|
43
|
+
message: "https://github.com/coorasse/passkit",
|
|
44
|
+
altText: "https://github.com/coorasse/passkit"}
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Barcode example
|
|
48
|
+
# def barcode
|
|
49
|
+
# { messageEncoding: 'iso-8859-1',
|
|
50
|
+
# format: 'PKBarcodeFormatCode128',
|
|
51
|
+
# message: '12345',
|
|
52
|
+
# altText: '12345' }
|
|
53
|
+
# end
|
|
54
|
+
|
|
55
|
+
def logo_text
|
|
56
|
+
"Loyalty Card"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def header_fields
|
|
60
|
+
[{
|
|
61
|
+
key: "balance",
|
|
62
|
+
label: "Balance",
|
|
63
|
+
value: 100,
|
|
64
|
+
currencyCode: "$"
|
|
65
|
+
}]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def back_fields
|
|
69
|
+
[{
|
|
70
|
+
key: "example1",
|
|
71
|
+
label: "Code",
|
|
72
|
+
value: "0123456789"
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
key: "example2",
|
|
76
|
+
label: "Creator",
|
|
77
|
+
value: "https://github.com/coorasse"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
key: "example3",
|
|
81
|
+
label: "Contact",
|
|
82
|
+
value: "rodi@hey.com"
|
|
83
|
+
}]
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def auxiliary_fields
|
|
87
|
+
[{
|
|
88
|
+
key: "name",
|
|
89
|
+
label: "Name",
|
|
90
|
+
value: "Alessandro Rodi"
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
key: "email",
|
|
94
|
+
label: "Email",
|
|
95
|
+
value: "rodi@hey.com"
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
key: "phone",
|
|
99
|
+
label: "Phone",
|
|
100
|
+
value: "+41 1234567890"
|
|
101
|
+
}]
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
private
|
|
105
|
+
|
|
106
|
+
def folder_name
|
|
107
|
+
self.class.name.demodulize.underscore
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
data/lib/passkit/factory.rb
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
module Passkit
|
|
2
2
|
class Factory
|
|
3
3
|
class << self
|
|
4
|
-
def create_pass(generator)
|
|
5
|
-
pass = Pass.create!(generator: generator)
|
|
4
|
+
def create_pass(pass_class, generator = nil)
|
|
5
|
+
pass = Pass.create!(klass: pass_class, generator: generator)
|
|
6
6
|
Passkit::Generator.new(pass).generate_and_sign
|
|
7
7
|
end
|
|
8
8
|
end
|
data/lib/passkit/generator.rb
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
|
+
require "zip"
|
|
2
|
+
|
|
1
3
|
module Passkit
|
|
2
4
|
class Generator
|
|
3
|
-
TMP_FOLDER = Rails.root.join(
|
|
5
|
+
TMP_FOLDER = Rails.root.join("tmp/passkit").freeze
|
|
4
6
|
|
|
5
|
-
def initialize(
|
|
7
|
+
def initialize(pass)
|
|
6
8
|
@pass = pass
|
|
7
9
|
@generator = pass.generator
|
|
8
10
|
end
|
|
9
11
|
|
|
10
12
|
def generate_and_sign
|
|
13
|
+
check_necessary_files
|
|
11
14
|
create_temporary_directory
|
|
12
15
|
copy_pass_to_tmp_location
|
|
13
16
|
clean_ds_store_files
|
|
@@ -21,9 +24,13 @@ module Passkit
|
|
|
21
24
|
|
|
22
25
|
private
|
|
23
26
|
|
|
27
|
+
def check_necessary_files
|
|
28
|
+
raise "icon.png is not present in #{@pass.pass_path}" unless File.exist?(File.join(@pass.pass_path, "icon.png"))
|
|
29
|
+
end
|
|
30
|
+
|
|
24
31
|
def create_temporary_directory
|
|
25
32
|
FileUtils.mkdir_p(TMP_FOLDER) unless File.directory?(TMP_FOLDER)
|
|
26
|
-
@temporary_path = TMP_FOLDER.join(@pass.file_name)
|
|
33
|
+
@temporary_path = TMP_FOLDER.join(@pass.file_name.to_s)
|
|
27
34
|
|
|
28
35
|
FileUtils.rm_rf(@temporary_path) if File.directory?(@temporary_path)
|
|
29
36
|
end
|
|
@@ -33,10 +40,9 @@ module Passkit
|
|
|
33
40
|
end
|
|
34
41
|
|
|
35
42
|
def clean_ds_store_files
|
|
36
|
-
Dir.glob(@temporary_path.join(
|
|
43
|
+
Dir.glob(@temporary_path.join("**/.DS_Store")).each { |file| File.delete(file) }
|
|
37
44
|
end
|
|
38
45
|
|
|
39
|
-
# rubocop:disable Metrics/AbcSize
|
|
40
46
|
def generate_json_pass
|
|
41
47
|
pass = {
|
|
42
48
|
formatVersion: @pass.format_version,
|
|
@@ -53,35 +59,37 @@ module Passkit
|
|
|
53
59
|
}
|
|
54
60
|
|
|
55
61
|
pass = pass.merge({
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
62
|
+
serialNumber: @pass.serial_number,
|
|
63
|
+
passTypeIdentifier: @pass.pass_type_identifier,
|
|
64
|
+
authenticationToken: @pass.authentication_token
|
|
65
|
+
})
|
|
60
66
|
|
|
61
67
|
pass[@pass.pass_type] = {
|
|
62
68
|
headerFields: @pass.header_fields,
|
|
69
|
+
primaryFields: @pass.primary_fields,
|
|
70
|
+
secondaryFields: @pass.secondary_fields,
|
|
63
71
|
auxiliaryFields: @pass.auxiliary_fields,
|
|
64
72
|
backFields: @pass.back_fields
|
|
65
73
|
}
|
|
66
74
|
|
|
67
|
-
File.
|
|
75
|
+
File.write(@temporary_path.join("pass.json"), pass.to_json)
|
|
68
76
|
end
|
|
69
77
|
|
|
70
78
|
# rubocop:enable Metrics/AbcSize
|
|
71
79
|
|
|
72
80
|
def generate_json_manifest
|
|
73
81
|
manifest = {}
|
|
74
|
-
Dir.glob(@temporary_path.join(
|
|
82
|
+
Dir.glob(@temporary_path.join("**")).each do |file|
|
|
75
83
|
manifest[File.basename(file)] = Digest::SHA1.hexdigest(File.read(file))
|
|
76
84
|
end
|
|
77
85
|
|
|
78
|
-
@manifest_url = @temporary_path.join(
|
|
79
|
-
File.
|
|
86
|
+
@manifest_url = @temporary_path.join("manifest.json")
|
|
87
|
+
File.write(@manifest_url, manifest.to_json)
|
|
80
88
|
end
|
|
81
89
|
|
|
82
|
-
CERTIFICATE = Rails.root.join(ENV[
|
|
83
|
-
INTERMEDIATE_CERTIFICATE = Rails.root.join(ENV[
|
|
84
|
-
CERTIFICATE_PASSWORD = ENV[
|
|
90
|
+
CERTIFICATE = Rails.root.join(ENV["PASSKIT_PRIVATE_P12_CERTIFICATE"])
|
|
91
|
+
INTERMEDIATE_CERTIFICATE = Rails.root.join(ENV["PASSKIT_APPLE_INTERMEDIATE_CERTIFICATE"])
|
|
92
|
+
CERTIFICATE_PASSWORD = ENV["PASSKIT_CERTIFICATE_KEY"]
|
|
85
93
|
|
|
86
94
|
# :nocov:
|
|
87
95
|
def sign_manifest
|
|
@@ -90,21 +98,21 @@ module Passkit
|
|
|
90
98
|
|
|
91
99
|
flag = OpenSSL::PKCS7::DETACHED | OpenSSL::PKCS7::BINARY
|
|
92
100
|
signed = OpenSSL::PKCS7.sign(p12_certificate.certificate,
|
|
93
|
-
|
|
94
|
-
|
|
101
|
+
p12_certificate.key, File.read(@manifest_url),
|
|
102
|
+
[intermediate_certificate], flag)
|
|
95
103
|
|
|
96
|
-
@signature_url = @temporary_path.join(
|
|
97
|
-
File.open(@signature_url,
|
|
104
|
+
@signature_url = @temporary_path.join("signature")
|
|
105
|
+
File.open(@signature_url, "w") { |f| f.syswrite signed.to_der }
|
|
98
106
|
end
|
|
99
107
|
|
|
100
108
|
# :nocov:
|
|
101
109
|
|
|
102
110
|
def compress_pass_file
|
|
103
111
|
zip_path = TMP_FOLDER.join("#{@pass.file_name}.pkpass")
|
|
104
|
-
zipped_file = File.open(zip_path,
|
|
112
|
+
zipped_file = File.open(zip_path, "w")
|
|
105
113
|
|
|
106
114
|
Zip::OutputStream.open(zipped_file.path) do |z|
|
|
107
|
-
Dir.glob(@temporary_path.join(
|
|
115
|
+
Dir.glob(@temporary_path.join("**")).each do |file|
|
|
108
116
|
z.put_next_entry(File.basename(file))
|
|
109
117
|
z.print File.read(file)
|
|
110
118
|
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Passkit
|
|
2
|
+
class PayloadGenerator
|
|
3
|
+
VALIDITY = 30.days
|
|
4
|
+
|
|
5
|
+
def self.encrypted(pass_class, generator = nil)
|
|
6
|
+
UrlEncrypt.encrypt(hash(pass_class, generator))
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def self.hash(pass_class, generator = nil)
|
|
10
|
+
valid_until = VALIDITY.from_now
|
|
11
|
+
|
|
12
|
+
{valid_until: valid_until,
|
|
13
|
+
generator_class: generator&.class&.name,
|
|
14
|
+
generator_id: generator&.id,
|
|
15
|
+
pass_class: pass_class.name}
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
data/lib/passkit/url_encrypt.rb
CHANGED
|
@@ -7,13 +7,13 @@ module Passkit
|
|
|
7
7
|
cipher.key = encryption_key
|
|
8
8
|
s = cipher.update(string) + cipher.final
|
|
9
9
|
|
|
10
|
-
s.unpack1(
|
|
10
|
+
s.unpack1("H*").upcase
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def decrypt(string)
|
|
14
14
|
cipher = cypher.decrypt
|
|
15
15
|
cipher.key = encryption_key
|
|
16
|
-
s = [string].pack(
|
|
16
|
+
s = [string].pack("H*").unpack("C*").pack("c*")
|
|
17
17
|
|
|
18
18
|
JSON.parse(cipher.update(s) + cipher.final, symbolize_names: true)
|
|
19
19
|
end
|
|
@@ -25,7 +25,7 @@ module Passkit
|
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
def cypher
|
|
28
|
-
OpenSSL::Cipher.new(
|
|
28
|
+
OpenSSL::Cipher.new("AES-128-CBC")
|
|
29
29
|
end
|
|
30
30
|
end
|
|
31
31
|
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Passkit
|
|
2
|
+
class UrlGenerator
|
|
3
|
+
include Passkit::Engine.routes.url_helpers
|
|
4
|
+
|
|
5
|
+
def initialize(pass_class, generator = nil)
|
|
6
|
+
@url = passes_api_url(host: ENV["PASSKIT_WEB_SERVICE_HOST"],
|
|
7
|
+
payload: PayloadGenerator.encrypted(pass_class, generator))
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def ios
|
|
11
|
+
@url
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
WALLET_PASS_PREFIX = "https://walletpass.io?u=".freeze
|
|
15
|
+
# @see https://walletpasses.io/developer/
|
|
16
|
+
def android
|
|
17
|
+
"#{WALLET_PASS_PREFIX}#{@url}"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
data/lib/passkit/version.rb
CHANGED
data/lib/passkit.rb
CHANGED
|
@@ -1,9 +1,43 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
require
|
|
3
|
+
require "rails"
|
|
4
|
+
require "passkit/engine"
|
|
5
|
+
|
|
6
|
+
require "zeitwerk"
|
|
7
|
+
loader = Zeitwerk::Loader.for_gem
|
|
8
|
+
loader.ignore("#{__dir__}/generators")
|
|
9
|
+
loader.setup
|
|
5
10
|
|
|
6
11
|
module Passkit
|
|
7
12
|
class Error < StandardError; end
|
|
8
|
-
|
|
13
|
+
|
|
14
|
+
class << self
|
|
15
|
+
attr_accessor :configuration
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.configure
|
|
19
|
+
self.configuration ||= Configuration.new
|
|
20
|
+
yield(configuration)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class Configuration
|
|
24
|
+
attr_accessor :available_passes,
|
|
25
|
+
:web_service_host,
|
|
26
|
+
:certificate_key,
|
|
27
|
+
:private_p12_certificate,
|
|
28
|
+
:apple_intermediate_certificate,
|
|
29
|
+
:apple_team_identifier,
|
|
30
|
+
:pass_type_identifier
|
|
31
|
+
|
|
32
|
+
def initialize
|
|
33
|
+
@available_passes = {"Passkit::ExampleStoreCard" => -> {}}
|
|
34
|
+
@web_service_host = ENV["PASSKIT_WEB_SERVICE_HOST"] || (raise "Please set PASSKIT_WEB_SERVICE_HOST")
|
|
35
|
+
raise("PASSKIT_WEB_SERVICE_HOST must start with https://") unless @web_service_host.start_with?("https://")
|
|
36
|
+
@certificate_key = ENV["PASSKIT_CERTIFICATE_KEY"] || (raise "Please set PASSKIT_CERTIFICATE_KEY")
|
|
37
|
+
@private_p12_certificate = ENV["PASSKIT_PRIVATE_P12_CERTIFICATE"] || (raise "Please set PASSKIT_PRIVATE_P12_CERTIFICATE")
|
|
38
|
+
@apple_intermediate_certificate = ENV["PASSKIT_APPLE_INTERMEDIATE_CERTIFICATE"] || (raise "Please set PASSKIT_APPLE_INTERMEDIATE_CERTIFICATE")
|
|
39
|
+
@apple_team_identifier = ENV["PASSKIT_APPLE_TEAM_IDENTIFIER"] || (raise "Please set PASSKIT_APPLE_TEAM_IDENTIFIER")
|
|
40
|
+
@pass_type_identifier = ENV["PASSKIT_PASS_TYPE_IDENTIFIER"] || (raise "Please set PASSKIT_PASS_TYPE_IDENTIFIER")
|
|
41
|
+
end
|
|
42
|
+
end
|
|
9
43
|
end
|
data/passkit-0.2.0.gem
ADDED
|
Binary file
|