1 | [![npm (scoped)](https://img.shields.io/npm/v/@destinationstransfers/passkit.svg)](https://www.npmjs.com/package/@destinationstransfers/passkit) [![codecov](https://codecov.io/gh/destinationstransfers/passkit/branch/master/graph/badge.svg)](https://codecov.io/gh/destinationstransfers/passkit)
|
2 | [![Build Status](https://dev.azure.com/destinationstransfers/passkit/_apis/build/status/destinationstransfers.passkit?branchName=master)](https://dev.azure.com/destinationstransfers/passkit/_build/latest?definitionId=2&branchName=master)
|
3 | [![Known Vulnerabilities](https://snyk.io/test/github/destinationstransfers/passkit/badge.svg)](https://snyk.io/test/github/destinationstransfers/passkit) [![DeepScan Grade](https://deepscan.io/api/projects/352/branches/551/badge/grade.svg)](https://deepscan.io/dashboard/#view=project&pid=352&bid=551)
|
4 |
|
5 | # Motivation
|
6 |
|
7 | This is complete rewrite of [assaf/node-passbook](http://github.com/assaf/node-passbook).
|
8 | The original module lacks new commits in last two years and outdated. This modules:
|
9 |
|
10 | - Targetting Node >= 10 and refactored in ES6 Classes, removing deprecated calls (`new Buffer`, etc)
|
11 | - Replaces `openssl` spawning with native Javascript RSA implementation (via `node-forge`)
|
12 | - Includes `Template.pushUpdates(pushToken)` that sends APN update request for a given pass type to a pushToken (get `pushToken` at your PassKit Web Service implementation)
|
13 | - Adds constants for dictionary fields string values
|
14 | - Migrated tests to Jest
|
15 | - Increased test coverage
|
16 | - Adds strict dictionary fields values validation (where possible) to prevent errors earlier
|
17 | - Adding support for geolocation fields and Becon fields
|
18 | - Adding easy template and localization load from JSON file
|
19 | - We use it in production at [Transfers.do](https://transfers.do/)
|
20 |
|
21 | # Get your certificates
|
22 |
|
23 | To start with, you'll need a certificate issued by [the iOS Provisioning
|
24 | Portal](https://developer.apple.com/ios/manage/passtypeids/index.action). You
|
25 | need one certificate per Pass Type ID.
|
26 |
|
27 | After adding this certificate to your Keychain, you need to export it as a
|
28 | `.p12` file and copy it into the keys directory.
|
29 |
|
30 | You will also need the [Apple Worldwide Developer Relations Certification
|
31 | Authority](https://www.apple.com/certificateauthority/) certificate and to convert the `.p12` files into `.pem` files. You
|
32 | can do both using the `passkit-keys` command:
|
33 |
|
34 | ```sh
|
35 | ./bin/passkit-keys ./pathToKeysFolder
|
36 | ```
|
37 |
|
38 | This is the same directory into which you placed the `.p12` files.
|
39 |
|
40 | # Start with a template
|
41 |
|
42 | Start with a template. A template has all the common data fields that will be
|
43 | shared between your passes, and also defines the keys to use for signing it.
|
44 |
|
45 | ```js
|
46 | const { Template } = require("@destinationstransfers/passkit");
|
47 |
|
48 | const template = new Template("coupon", {
|
49 | passTypeIdentifier: "pass.com.example.passbook",
|
50 | teamIdentifier: "MXL",
|
51 | backgroundColor: "rgb(255,255,255)"
|
52 | });
|
53 |
|
54 | // or
|
55 |
|
56 | const template = await Template.load('./path/to/templateFolder', 'secretKeyPasswod');
|
57 | // .load will load all "templateable" fields from pass.json,
|
58 | // as well as all images and com.example.passbook.pem file as key
|
59 | ```
|
60 |
|
61 | The first argument is the pass style (`coupon`, `eventTicket`, etc), and the
|
62 | second optional argument has any fields you want to set on the template.
|
63 |
|
64 | You can access template fields directly, or from chained accessor methods, e.g:
|
65 |
|
66 | ```js
|
67 | template.fields.passTypeIdentifier = "pass.com.example.passbook";
|
68 |
|
69 | console.log(template.passTypeIdentifier());
|
70 |
|
71 | template.teamIdentifier("MXL").
|
72 | passTypeIdentifier("pass.com.example.passbook")
|
73 | ```
|
74 |
|
75 | The following template fields are required:
|
76 | `passTypeIdentifier` - The Apple Pass Type ID, begins with "pass."
|
77 | `teamIdentifier` - May contain an I
|
78 |
|
79 | Optional fields that you can set on the template (or pass): `backgroundColor`,
|
80 | `foregroundColor`, `labelColor`, `logoText`, `organizationName`,
|
81 | `suppressStripShine` and `webServiceURL`.
|
82 |
|
83 | In addition, you need to tell the template where to find the key files and where
|
84 | to load images from:
|
85 |
|
86 | ```js
|
87 | await template.loadCertificate("/etc/passbook/certificate_and_key.pem", "secret");
|
88 | await template.images.loadFromDirectory("./images"); // loadFromDirectory returns Promise
|
89 | ```
|
90 |
|
91 | The last part is optional, but if you have images that are common to all passes,
|
92 | you may want to specify them once in the template.
|
93 |
|
94 | # Create your pass
|
95 |
|
96 | To create a new pass from a template:
|
97 |
|
98 | ```js
|
99 | const pass = template.createPass({
|
100 | serialNumber: "123456",
|
101 | description: "20% off"
|
102 | });
|
103 | ```
|
104 |
|
105 | Just like template, you can access pass fields directly, or from chained
|
106 | accessor methods, e.g:
|
107 |
|
108 | ```js
|
109 | pass.fields.serialNumber = "12345";
|
110 | console.log(pass.serialNumber());
|
111 | pass.serialNumber("12345").
|
112 | description("20% off");
|
113 | ```
|
114 |
|
115 | In the JSON specification, structure fields (primary fields, secondary fields,
|
116 | etc) are represented as arrays, but items must have distinct key properties. Le
|
117 | sigh.
|
118 |
|
119 | To make it easier, you can use methods like `add`, `get` and `remove` that
|
120 | will do the logical thing. For example, to add a primary field:
|
121 |
|
122 | ```js
|
123 | pass.primaryFields.add("date", "Date", "Nov 1");
|
124 | pass.primaryFields.add({ key: "time", label: "Time", value: "10:00AM");
|
125 | ```
|
126 |
|
127 | You can also call `add` with an array of triplets or array of objects.
|
128 |
|
129 | To get one or all fields:
|
130 |
|
131 | ```js
|
132 | const dateField = pass.primaryFields.get("date");
|
133 | const allFields = pass.primaryFields.all();
|
134 | ```
|
135 |
|
136 | To remove one or all fields:
|
137 |
|
138 | ```js
|
139 | pass.primaryFields.remove("date");
|
140 | pass.primaryFields.clear();
|
141 | ```
|
142 |
|
143 | Adding images to a pass is the same as adding images to a template:
|
144 |
|
145 | ```js
|
146 | pass.images.icon = iconFilename;
|
147 | pass.icon(iconFilename);
|
148 | await pass.images.loadFromDirectory('./images');
|
149 | ```
|
150 |
|
151 | You can add the image itself or a `Buffer`. You can also provide a function that will
|
152 | be called when it's time to load the image, and should pass an error, or `null`
|
153 | and a buffer to its callback.
|
154 |
|
155 | Additionally localizations can be added if needed (images localizations are not supported at the moment, but will be added soon):
|
156 |
|
157 | ```js
|
158 | pass.addLocalization("en", {
|
159 | "GATE": "GATE",
|
160 | "DEPART": "DEPART",
|
161 | "ARRIVE": "ARRIVE",
|
162 | "SEAT": "SEAT",
|
163 | "PASSENGER": "PASSENGER",
|
164 | "FLIGHT": "FLIGHT"
|
165 | });
|
166 |
|
167 | pass.addLocalization("ru", {
|
168 | "GATE": "ВЫХОД",
|
169 | "DEPART": "ВЫЛЕТ",
|
170 | "ARRIVE": "ПРИЛЁТ",
|
171 | "SEAT": "МЕСТО",
|
172 | "PASSENGER": "ПАССАЖИР",
|
173 | "FLIGHT": "РЕЙС"
|
174 | });
|
175 | ```
|
176 |
|
177 | Localization applies for all fields' `label` and `value`. There is a note about that in [documentation](https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/PassKit_PG/Creating.html).
|
178 |
|
179 | # Generate the file
|
180 |
|
181 | To generate a file:
|
182 |
|
183 | ```js
|
184 | const file = fs.createWriteStream("mypass.pkpass");
|
185 | pass.on("error", error => {
|
186 | console.error(error);
|
187 | process.exit(1);
|
188 | })
|
189 | pass.pipe(file);
|
190 | ```
|
191 |
|
192 | Your pass will emit the `error` event if it fails to generate a valid Passbook
|
193 | file, and emit the `end` event when it successfully completed generating the
|
194 | file.
|
195 |
|
196 | You can pipe to any writable stream. When working with HTTP, the `render`
|
197 | method will set the content type, pipe to the HTTP response, and make use of a
|
198 | callback (if supplied).
|
199 |
|
200 | ```js
|
201 | server.get("/mypass", (request, response) => {
|
202 | pass.render(response, error => {
|
203 | if (error)
|
204 | console.error(error);
|
205 | });
|
206 | });
|
207 | ```
|