mirror of
https://github.com/marcogll/passkit-generator.git
synced 2026-03-15 13:25:19 +00:00
Completely refactored examples to run only once and have multiple endpoints
This commit is contained in:
35
examples/self-hosted/README.md
Normal file
35
examples/self-hosted/README.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Examples
|
||||
|
||||
This is examples folder. These examples are used to test new features and as sample showcases.
|
||||
|
||||
Each example owns an endpoint where a pass can be reached. This project is build upon Express.js, which is required to be installed.
|
||||
|
||||
Typescript compilation is done automatically through `ts-node`.
|
||||
|
||||
Assuming you already have cloned this repository, installed its dependencies through `npm install` and moved to `examples/self-hosted`, run these commands:
|
||||
|
||||
```sh
|
||||
$ npm install;
|
||||
$ npm run example;
|
||||
```
|
||||
|
||||
Certificates paths in examples are linked to a folder `certificates` in the root of this project which is not provided.
|
||||
To make them work, you'll have to edit both certificates and model path.
|
||||
|
||||
Every example runs on `0.0.0.0:8080`. Visit `http://localhost:8080/:example/:modelName`, by replacing `:example` with one of the following and `:modelName` with one inside models folder.
|
||||
|
||||
Please note that `field.js` example will force you to download `exampleBooking.pass`, no matter what.
|
||||
|
||||
| Example name | Endpoint name | Additional notes |
|
||||
| -------------- | ----------------- | --------------------------------------------------------------------------------------------------------------------------- |
|
||||
| localize | `/localize` | - |
|
||||
| fields | `/fields` | - |
|
||||
| expirationDate | `/expirationDate` | Accepts a required parameter in query string `fn`, which can be either `expiration` or `void`, to switch generated example. |
|
||||
| scratch | `/scratch` | - |
|
||||
| PKPass.from | pkpassfrom | - |
|
||||
| barcodes | `/barcodes` | Using `?alt=true` query parameter, will lead to barcode string message usage instead of selected ones |
|
||||
| pkpasses | `/pkpasses` | - |
|
||||
|
||||
---
|
||||
|
||||
Every contribution is really appreciated. ❤️ Thank you!
|
||||
1590
examples/self-hosted/package-lock.json
generated
Normal file
1590
examples/self-hosted/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
28
examples/self-hosted/package.json
Normal file
28
examples/self-hosted/package.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "examples-self-hosted",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"description": "Passkit-generator self-hosted examples",
|
||||
"author": "Alexander P. Cerutti <cerutti.alexander@gmail.com>",
|
||||
"license": "ISC",
|
||||
"scripts": {
|
||||
"preinstall": "npm run clear:deps && npm unlink --no-save passkit-generator",
|
||||
"postinstall": "npm --prefix ../.. run build && npm --prefix ../.. link && npm link passkit-generator",
|
||||
"example": "npx ts-node src/index.ts",
|
||||
"example:debug": "node -r ts-node/register --inspect-brk src/index.ts",
|
||||
"clear:deps": "rm -rf node_modules"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"passkit-generator": "latest"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^4.17.1",
|
||||
"node-fetch": "^3.0.0",
|
||||
"tslib": "^2.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "4.17.8",
|
||||
"ts-node": "^10.4.0",
|
||||
"typescript": "^4.4.4"
|
||||
}
|
||||
}
|
||||
148
examples/self-hosted/src/PKPass.from.ts
Normal file
148
examples/self-hosted/src/PKPass.from.ts
Normal file
@@ -0,0 +1,148 @@
|
||||
/**
|
||||
* PKPass.from static method example.
|
||||
* Here it is showed manual model reading and
|
||||
* creating through another PKPass because in the other
|
||||
* examples, creation through templates is already shown
|
||||
*/
|
||||
|
||||
import { app } from "./webserver";
|
||||
import { getCertificates } from "./shared";
|
||||
import path from "path";
|
||||
import { promises as fs } from "fs";
|
||||
import { PKPass } from "passkit-generator";
|
||||
|
||||
import * as Utils from "passkit-generator/lib/utils";
|
||||
|
||||
// ******************************************** //
|
||||
// *** CODE FROM GET MODEL FOLDER INTERNALS *** //
|
||||
// ******************************************** //
|
||||
|
||||
async function readFileOrDirectory(filePath: string) {
|
||||
if ((await fs.lstat(filePath)).isDirectory()) {
|
||||
return Promise.all(await readDirectory(filePath));
|
||||
} else {
|
||||
return fs
|
||||
.readFile(filePath)
|
||||
.then((content) => getObjectFromModelFile(filePath, content, 1));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object containing the parsed fileName
|
||||
* from a path along with its content.
|
||||
*
|
||||
* @param filePath
|
||||
* @param content
|
||||
* @param depthFromEnd - used to preserve localization lproj content
|
||||
* @returns
|
||||
*/
|
||||
|
||||
function getObjectFromModelFile(
|
||||
filePath: string,
|
||||
content: Buffer,
|
||||
depthFromEnd: number,
|
||||
) {
|
||||
const fileComponents = filePath.split(path.sep);
|
||||
const fileName = fileComponents
|
||||
.slice(fileComponents.length - depthFromEnd)
|
||||
.join(path.sep);
|
||||
|
||||
return { [fileName]: content };
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a directory and returns all the files in it
|
||||
* as an Array<Promise>
|
||||
*
|
||||
* @param filePath
|
||||
* @returns
|
||||
*/
|
||||
|
||||
async function readDirectory(filePath: string) {
|
||||
const dirContent = await fs.readdir(filePath).then(Utils.removeHidden);
|
||||
|
||||
return dirContent.map(async (fileName) => {
|
||||
const content = await fs.readFile(path.resolve(filePath, fileName));
|
||||
return getObjectFromModelFile(
|
||||
path.resolve(filePath, fileName),
|
||||
content,
|
||||
2,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// *************************** //
|
||||
// *** EXAMPLE FROM NOW ON *** //
|
||||
// *************************** //
|
||||
|
||||
const passTemplate = new Promise<PKPass>(async (resolve) => {
|
||||
const modelPath = path.resolve(__dirname, `../../models/examplePass.pass`);
|
||||
const [modelFilesList, certificates] = await Promise.all([
|
||||
fs.readdir(modelPath),
|
||||
getCertificates(),
|
||||
]);
|
||||
|
||||
const modelRecords = (
|
||||
await Promise.all(
|
||||
/**
|
||||
* Obtaining flattened array of buffer records
|
||||
* containing file name and the buffer itself.
|
||||
*
|
||||
* This goes also to read every nested l10n
|
||||
* subfolder.
|
||||
*/
|
||||
|
||||
modelFilesList.map((fileOrDirectoryPath) => {
|
||||
const fullPath = path.resolve(modelPath, fileOrDirectoryPath);
|
||||
|
||||
return readFileOrDirectory(fullPath);
|
||||
}),
|
||||
)
|
||||
)
|
||||
.flat(1)
|
||||
.reduce((acc, current) => ({ ...acc, ...current }), {});
|
||||
|
||||
/** Creating a PKPass Template */
|
||||
|
||||
return resolve(
|
||||
new PKPass(modelRecords, {
|
||||
wwdr: certificates.wwdr,
|
||||
signerCert: certificates.signerCert,
|
||||
signerKey: certificates.signerKey,
|
||||
signerKeyPassphrase: certificates.signerKeyPassphrase,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
app.route("/pkpassfrom/:modelName").get(async (request, response) => {
|
||||
const passName =
|
||||
request.params.modelName +
|
||||
"_" +
|
||||
new Date().toISOString().split("T")[0].replace(/-/gi, "");
|
||||
|
||||
const templatePass = await passTemplate;
|
||||
|
||||
try {
|
||||
const pass = await PKPass.from(
|
||||
templatePass,
|
||||
request.body || request.params || request.query,
|
||||
);
|
||||
|
||||
const stream = pass.getAsStream();
|
||||
|
||||
response.set({
|
||||
"Content-type": pass.mimeType,
|
||||
"Content-disposition": `attachment; filename=${passName}.pkpass`,
|
||||
});
|
||||
|
||||
stream.pipe(response);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
|
||||
response.set({
|
||||
"Content-type": "text/html",
|
||||
});
|
||||
|
||||
response.send(err.message);
|
||||
}
|
||||
});
|
||||
151
examples/self-hosted/src/PKPasses.ts
Normal file
151
examples/self-hosted/src/PKPasses.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
/**
|
||||
* PKPasses generation through PKPass.pack static method
|
||||
* example.
|
||||
* Here it is showed manual model reading and
|
||||
* creating through another PKPass because in the other
|
||||
* examples, creation through templates is already shown
|
||||
*
|
||||
* PLEASE NOTE THAT, AT TIME OF WRITING, THIS EXAMPLE WORKS
|
||||
* ONLY IF PASSES ARE DOWNLOADED FROM SAFARI, due to the
|
||||
* support of PKPasses archives. To test this, you might
|
||||
* need to open a tunnel through NGROK if you cannot access
|
||||
* to your local machine (in my personal case, developing
|
||||
* under WSL is a pretty big limitation sometimes).
|
||||
*
|
||||
* @TODO test again this example with next iOS 15 versions.
|
||||
* Currently, pass viewer seems to be soooo bugged.
|
||||
*
|
||||
* https://imgur.com/bDTbcDg.jpg
|
||||
* https://imgur.com/Y4GpuHT.jpg
|
||||
* https://i.imgur.com/qbJMy1d.jpg
|
||||
*
|
||||
* Alberto, come to look at APPLE.
|
||||
*
|
||||
* MAMMA MIA!
|
||||
*
|
||||
* A feedback to Apple have been sent for this.
|
||||
*/
|
||||
|
||||
import { app } from "./webserver";
|
||||
import { getCertificates } from "./shared";
|
||||
import { promises as fs } from "fs";
|
||||
import path from "path";
|
||||
import { PKPass } from "passkit-generator";
|
||||
|
||||
// *************************** //
|
||||
// *** EXAMPLE FROM NOW ON *** //
|
||||
// *************************** //
|
||||
|
||||
function getRandomColorPart() {
|
||||
return Math.floor(Math.random() * 255);
|
||||
}
|
||||
|
||||
async function generatePass(props: Object) {
|
||||
const [iconFromModel, certificates] = await Promise.all([
|
||||
fs.readFile(
|
||||
path.resolve(
|
||||
__dirname,
|
||||
"../../models/exampleBooking.pass/icon.png",
|
||||
),
|
||||
),
|
||||
getCertificates(),
|
||||
]);
|
||||
|
||||
const pass = new PKPass(
|
||||
{},
|
||||
{
|
||||
wwdr: certificates.wwdr,
|
||||
signerCert: certificates.signerCert,
|
||||
signerKey: certificates.signerKey,
|
||||
signerKeyPassphrase: certificates.signerKeyPassphrase,
|
||||
},
|
||||
{
|
||||
...props,
|
||||
description: "Example Apple Wallet Pass",
|
||||
passTypeIdentifier: "pass.com.passkitgenerator",
|
||||
serialNumber: "nmyuxofgna",
|
||||
organizationName: `Test Organization ${Math.random()}`,
|
||||
teamIdentifier: "F53WB8AE67",
|
||||
foregroundColor: `rgb(${getRandomColorPart()}, ${getRandomColorPart()}, ${getRandomColorPart()})`,
|
||||
labelColor: `rgb(${getRandomColorPart()}, ${getRandomColorPart()}, ${getRandomColorPart()})`,
|
||||
backgroundColor: `rgb(${getRandomColorPart()}, ${getRandomColorPart()}, ${getRandomColorPart()})`,
|
||||
},
|
||||
);
|
||||
|
||||
pass.type = "boardingPass";
|
||||
pass.transitType = "PKTransitTypeAir";
|
||||
|
||||
pass.setBarcodes({
|
||||
message: "123456789",
|
||||
format: "PKBarcodeFormatQR",
|
||||
});
|
||||
|
||||
pass.headerFields.push(
|
||||
{
|
||||
key: "header-field-test-1",
|
||||
value: "Unknown",
|
||||
},
|
||||
{
|
||||
key: "header-field-test-2",
|
||||
value: "unknown",
|
||||
},
|
||||
);
|
||||
|
||||
pass.primaryFields.push(
|
||||
{
|
||||
key: "primaryField-1",
|
||||
value: "NAP",
|
||||
},
|
||||
{
|
||||
key: "primaryField-2",
|
||||
value: "VCE",
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* Required by Apple. If one is not available, a
|
||||
* pass might be openable on a Mac but not on a
|
||||
* specific iPhone model
|
||||
*/
|
||||
|
||||
pass.addBuffer("icon.png", iconFromModel);
|
||||
pass.addBuffer("icon@2x.png", iconFromModel);
|
||||
pass.addBuffer("icon@3x.png", iconFromModel);
|
||||
|
||||
return pass;
|
||||
}
|
||||
|
||||
app.route("/pkpasses/:modelName").get(async (request, response) => {
|
||||
const passName =
|
||||
request.params.modelName +
|
||||
"_" +
|
||||
new Date().toISOString().split("T")[0].replace(/-/gi, "");
|
||||
|
||||
try {
|
||||
const passes = await Promise.all([
|
||||
generatePass(request.body || request.params || request.query),
|
||||
generatePass(request.body || request.params || request.query),
|
||||
generatePass(request.body || request.params || request.query),
|
||||
generatePass(request.body || request.params || request.query),
|
||||
]);
|
||||
|
||||
const pkpasses = PKPass.pack(...passes);
|
||||
|
||||
response.set({
|
||||
"Content-type": pkpasses.mimeType,
|
||||
"Content-disposition": `attachment; filename=${passName}.pkpasses`,
|
||||
});
|
||||
|
||||
const stream = pkpasses.getAsStream();
|
||||
|
||||
stream.pipe(response);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
|
||||
response.set({
|
||||
"Content-type": "text/html",
|
||||
});
|
||||
|
||||
response.send(err.message);
|
||||
}
|
||||
});
|
||||
199
examples/self-hosted/src/fields.ts
Normal file
199
examples/self-hosted/src/fields.ts
Normal file
@@ -0,0 +1,199 @@
|
||||
/**
|
||||
* Fields pushing dimostration
|
||||
* To see all the included Fields, just open the pass
|
||||
* Refer to https://apple.co/2Nvshvn to see how passes
|
||||
* have their fields disposed.
|
||||
*
|
||||
* In this example we are going to imitate an EasyJet boarding pass
|
||||
*
|
||||
* @Author: Alexander P. Cerutti
|
||||
*/
|
||||
|
||||
import { app } from "./webserver";
|
||||
import { getCertificates } from "./shared";
|
||||
import path from "path";
|
||||
import { PKPass } from "passkit-generator";
|
||||
|
||||
app.route("/fields/:modelName").get(async (request, response) => {
|
||||
const passName =
|
||||
"exampleBooking" +
|
||||
"_" +
|
||||
new Date().toISOString().split("T")[0].replace(/-/gi, "");
|
||||
|
||||
const certificates = await getCertificates();
|
||||
|
||||
try {
|
||||
const pass = await PKPass.from(
|
||||
{
|
||||
model: path.resolve(__dirname, "../../models/exampleBooking"),
|
||||
certificates: {
|
||||
wwdr: certificates.wwdr,
|
||||
signerCert: certificates.signerCert,
|
||||
signerKey: certificates.signerKey,
|
||||
signerKeyPassphrase: certificates.signerKeyPassphrase,
|
||||
},
|
||||
},
|
||||
request.body || request.params || request.query,
|
||||
);
|
||||
|
||||
pass.transitType = "PKTransitTypeAir";
|
||||
|
||||
pass.headerFields.push(
|
||||
{
|
||||
key: "header1",
|
||||
label: "Data",
|
||||
value: "25 mag",
|
||||
textAlignment: "PKTextAlignmentCenter",
|
||||
},
|
||||
{
|
||||
key: "header2",
|
||||
label: "Volo",
|
||||
value: "EZY997",
|
||||
textAlignment: "PKTextAlignmentCenter",
|
||||
},
|
||||
);
|
||||
|
||||
pass.primaryFields.push(
|
||||
{
|
||||
key: "IATA-source",
|
||||
value: "NAP",
|
||||
label: "Napoli",
|
||||
textAlignment: "PKTextAlignmentLeft",
|
||||
},
|
||||
{
|
||||
key: "IATA-destination",
|
||||
value: "VCE",
|
||||
label: "Venezia Marco Polo",
|
||||
textAlignment: "PKTextAlignmentRight",
|
||||
},
|
||||
);
|
||||
|
||||
pass.secondaryFields.push(
|
||||
{
|
||||
key: "secondary1",
|
||||
label: "Imbarco chiuso",
|
||||
value: "18:40",
|
||||
textAlignment: "PKTextAlignmentCenter",
|
||||
},
|
||||
{
|
||||
key: "sec2",
|
||||
label: "Partenze",
|
||||
value: "19:10",
|
||||
textAlignment: "PKTextAlignmentCenter",
|
||||
},
|
||||
{
|
||||
key: "sec3",
|
||||
label: "SB",
|
||||
value: "Sì",
|
||||
textAlignment: "PKTextAlignmentCenter",
|
||||
},
|
||||
{
|
||||
key: "sec4",
|
||||
label: "Imbarco",
|
||||
value: "Anteriore",
|
||||
textAlignment: "PKTextAlignmentCenter",
|
||||
},
|
||||
);
|
||||
|
||||
pass.auxiliaryFields.push(
|
||||
{
|
||||
key: "aux1",
|
||||
label: "Passeggero",
|
||||
value: "MR. WHO KNOWS",
|
||||
textAlignment: "PKTextAlignmentLeft",
|
||||
},
|
||||
{
|
||||
key: "aux2",
|
||||
label: "Posto",
|
||||
value: "1A*",
|
||||
textAlignment: "PKTextAlignmentCenter",
|
||||
},
|
||||
);
|
||||
|
||||
pass.backFields.push(
|
||||
{
|
||||
key: "document number",
|
||||
label: "Numero documento:",
|
||||
value: "- -",
|
||||
textAlignment: "PKTextAlignmentLeft",
|
||||
},
|
||||
{
|
||||
key: "You're checked in, what next",
|
||||
label: "Hai effettuato il check-in, Quali sono le prospettive",
|
||||
value: "",
|
||||
textAlignment: "PKTextAlignmentLeft",
|
||||
},
|
||||
{
|
||||
key: "Check In",
|
||||
label: "1. check-in✓",
|
||||
value: "",
|
||||
textAlignment: "PKTextAlignmentLeft",
|
||||
},
|
||||
{
|
||||
key: "checkIn",
|
||||
label: "",
|
||||
value: "Le uscite d'imbarco chiudono 30 minuti prima della partenza, quindi sii puntuale. In questo aeroporto puoi utilizzare la corsia Fast Track ai varchi di sicurezza.",
|
||||
textAlignment: "PKTextAlignmentLeft",
|
||||
},
|
||||
{
|
||||
key: "2. Bags",
|
||||
label: "2. Bagaglio",
|
||||
value: "",
|
||||
textAlignment: "PKTextAlignmentLeft",
|
||||
},
|
||||
{
|
||||
key: "Require special assistance",
|
||||
label: "Assistenza speciale",
|
||||
value: "Se hai richiesto assistenza speciale, presentati a un membro del personale nell'area di Consegna bagagli almeno 90 minuti prima del volo.",
|
||||
textAlignment: "PKTextAlignmentLeft",
|
||||
},
|
||||
{
|
||||
key: "3. Departures",
|
||||
label: "3. Partenze",
|
||||
value: "",
|
||||
textAlignment: "PKTextAlignmentLeft",
|
||||
},
|
||||
{
|
||||
key: "photoId",
|
||||
label: "Un documento d’identità corredato di fotografia",
|
||||
value: "è obbligatorio su TUTTI i voli. Per un viaggio internazionale è necessario un passaporto valido o, dove consentita, una carta d’identità.",
|
||||
textAlignment: "PKTextAlignmentLeft",
|
||||
},
|
||||
{
|
||||
key: "yourSeat",
|
||||
label: "Il tuo posto:",
|
||||
value: "verifica il tuo numero di posto nella parte superiore. Durante l’imbarco utilizza le scale anteriori e posteriori: per le file 1-10 imbarcati dalla parte anteriore; per le file 11-31 imbarcati dalla parte posteriore. Colloca le borse di dimensioni ridotte sotto il sedile davanti a te.",
|
||||
textAlignment: "PKTextAlignmentLeft",
|
||||
},
|
||||
{
|
||||
key: "Pack safely",
|
||||
label: "Bagaglio sicuro",
|
||||
value: "Fai clic http://easyjet.com/it/articoli-pericolosi per maggiori informazioni sulle merci pericolose oppure visita il sito CAA http://www.caa.co.uk/default.aspx?catid=2200",
|
||||
textAlignment: "PKTextAlignmentLeft",
|
||||
},
|
||||
{
|
||||
key: "Thank you for travelling easyJet",
|
||||
label: "Grazie per aver viaggiato con easyJet",
|
||||
value: "",
|
||||
textAlignment: "PKTextAlignmentLeft",
|
||||
},
|
||||
);
|
||||
|
||||
const stream = pass.getAsStream();
|
||||
|
||||
response.set({
|
||||
"Content-type": pass.mimeType,
|
||||
"Content-disposition": `attachment; filename=${passName}.pkpass`,
|
||||
});
|
||||
|
||||
stream.pipe(response);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
|
||||
response.set({
|
||||
"Content-type": "text/html",
|
||||
});
|
||||
|
||||
response.send(err.message);
|
||||
}
|
||||
});
|
||||
7
examples/self-hosted/src/index.ts
Normal file
7
examples/self-hosted/src/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import "./fields";
|
||||
import "./localize";
|
||||
import "./PKPass.from";
|
||||
import "./PKPasses";
|
||||
import "./scratch";
|
||||
import "./setBarcodes";
|
||||
import "./setExpirationDate";
|
||||
76
examples/self-hosted/src/localize.ts
Normal file
76
examples/self-hosted/src/localize.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* .localize() methods example
|
||||
* To see all the included languages, you have to unzip the
|
||||
* .pkpass file and check for .lproj folders
|
||||
*/
|
||||
|
||||
import { app } from "./webserver";
|
||||
import { getCertificates } from "./shared";
|
||||
import path from "path";
|
||||
import { PKPass } from "passkit-generator";
|
||||
/** Symbols are exported just for tests and examples. Replicate only if really needed. */
|
||||
import { localizationSymbol } from "passkit-generator/lib/PKPass";
|
||||
|
||||
app.route("/localize/:modelName").get(async (request, response) => {
|
||||
const passName =
|
||||
request.params.modelName +
|
||||
"_" +
|
||||
new Date().toISOString().split("T")[0].replace(/-/gi, "");
|
||||
|
||||
const certificates = await getCertificates();
|
||||
|
||||
try {
|
||||
const pass = await PKPass.from(
|
||||
{
|
||||
model: path.resolve(
|
||||
__dirname,
|
||||
`../../models/${request.params.modelName}`,
|
||||
),
|
||||
certificates: {
|
||||
wwdr: certificates.wwdr,
|
||||
signerCert: certificates.signerCert,
|
||||
signerKey: certificates.signerKey,
|
||||
signerKeyPassphrase: certificates.signerKeyPassphrase,
|
||||
},
|
||||
},
|
||||
request.body || request.params || request.query,
|
||||
);
|
||||
|
||||
// Italian, already has an .lproj which gets included...
|
||||
pass.localize("it", {
|
||||
EVENT: "Evento",
|
||||
LOCATION: "Dove",
|
||||
});
|
||||
|
||||
// ...while German doesn't, so it gets created
|
||||
pass.localize("de", {
|
||||
EVENT: "Ereignis",
|
||||
LOCATION: "Ort",
|
||||
});
|
||||
|
||||
// This language does not exist but is still added as .lproj folder
|
||||
pass.localize("zu", {});
|
||||
|
||||
console.log(
|
||||
"Added languages",
|
||||
Object.keys(pass[localizationSymbol]).join(", "),
|
||||
);
|
||||
|
||||
const stream = pass.getAsStream();
|
||||
|
||||
response.set({
|
||||
"Content-type": pass.mimeType,
|
||||
"Content-disposition": `attachment; filename=${passName}.pkpass`,
|
||||
});
|
||||
|
||||
stream.pipe(response);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
|
||||
response.set({
|
||||
"Content-type": "text/html",
|
||||
});
|
||||
|
||||
response.send(err.message);
|
||||
}
|
||||
});
|
||||
106
examples/self-hosted/src/scratch.ts
Normal file
106
examples/self-hosted/src/scratch.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* This examples shows how you can create a PKPass from scratch,
|
||||
* by adding files later and not adding pass.json
|
||||
*/
|
||||
|
||||
import { app } from "./webserver";
|
||||
import { getCertificates } from "./shared";
|
||||
import path from "path";
|
||||
import { promises as fs } from "fs";
|
||||
import { PKPass } from "passkit-generator";
|
||||
|
||||
function getRandomColorPart() {
|
||||
return Math.floor(Math.random() * 255);
|
||||
}
|
||||
|
||||
app.route("/scratch/:modelName").get(async (request, response) => {
|
||||
const passName =
|
||||
request.params.modelName +
|
||||
"_" +
|
||||
new Date().toISOString().split("T")[0].replace(/-/gi, "");
|
||||
|
||||
const [iconFromModel, certificates] = await Promise.all([
|
||||
fs.readFile(
|
||||
path.resolve(
|
||||
__dirname,
|
||||
"../../models/exampleBooking.pass/icon.png",
|
||||
),
|
||||
),
|
||||
await getCertificates(),
|
||||
]);
|
||||
|
||||
try {
|
||||
const pass = new PKPass(
|
||||
{},
|
||||
{
|
||||
wwdr: certificates.wwdr,
|
||||
signerCert: certificates.signerCert,
|
||||
signerKey: certificates.signerKey,
|
||||
signerKeyPassphrase: certificates.signerKeyPassphrase,
|
||||
},
|
||||
{
|
||||
...(request.body || request.params || request.query),
|
||||
description: "Example Apple Wallet Pass",
|
||||
passTypeIdentifier: "pass.com.passkitgenerator",
|
||||
serialNumber: "nmyuxofgna",
|
||||
organizationName: `Test Organization ${Math.random()}`,
|
||||
teamIdentifier: "F53WB8AE67",
|
||||
foregroundColor: `rgb(${getRandomColorPart()}, ${getRandomColorPart()}, ${getRandomColorPart()})`,
|
||||
labelColor: `rgb(${getRandomColorPart()}, ${getRandomColorPart()}, ${getRandomColorPart()})`,
|
||||
backgroundColor: `rgb(${getRandomColorPart()}, ${getRandomColorPart()}, ${getRandomColorPart()})`,
|
||||
},
|
||||
);
|
||||
|
||||
pass.type = "boardingPass";
|
||||
pass.transitType = "PKTransitTypeAir";
|
||||
|
||||
pass.headerFields.push(
|
||||
{
|
||||
key: "header-field-test-1",
|
||||
value: "Unknown",
|
||||
},
|
||||
{
|
||||
key: "header-field-test-2",
|
||||
value: "unknown",
|
||||
},
|
||||
);
|
||||
|
||||
pass.primaryFields.push(
|
||||
{
|
||||
key: "primaryField-1",
|
||||
value: "NAP",
|
||||
},
|
||||
{
|
||||
key: "primaryField-2",
|
||||
value: "VCE",
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* Required by Apple. If one is not available, a
|
||||
* pass might be openable on a Mac but not on a
|
||||
* specific iPhone model
|
||||
*/
|
||||
|
||||
pass.addBuffer("icon.png", iconFromModel);
|
||||
pass.addBuffer("icon@2x.png", iconFromModel);
|
||||
pass.addBuffer("icon@3x.png", iconFromModel);
|
||||
|
||||
const stream = pass.getAsStream();
|
||||
|
||||
response.set({
|
||||
"Content-type": pass.mimeType,
|
||||
"Content-disposition": `attachment; filename=${passName}.pkpass`,
|
||||
});
|
||||
|
||||
stream.pipe(response);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
|
||||
response.set({
|
||||
"Content-type": "text/html",
|
||||
});
|
||||
|
||||
response.send(err.message);
|
||||
}
|
||||
});
|
||||
82
examples/self-hosted/src/setBarcodes.ts
Normal file
82
examples/self-hosted/src/setBarcodes.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* .barcodes() methods example
|
||||
* Here we set the barcode. To see all the results, you can
|
||||
* both unzip .pkpass file or check the properties before
|
||||
* generating the whole bundle
|
||||
*
|
||||
* Pass ?alt=true as querystring to test a barcode generate
|
||||
* by a string
|
||||
*/
|
||||
|
||||
import { app } from "./webserver";
|
||||
import { getCertificates } from "./shared";
|
||||
import { PKPass } from "passkit-generator";
|
||||
import path from "path";
|
||||
|
||||
app.route("/barcodes/:modelName").get(async (request, response) => {
|
||||
const passName =
|
||||
request.params.modelName +
|
||||
"_" +
|
||||
new Date().toISOString().split("T")[0].replace(/-/gi, "");
|
||||
|
||||
const certificates = await getCertificates();
|
||||
|
||||
try {
|
||||
const pass = await PKPass.from(
|
||||
{
|
||||
model: path.resolve(
|
||||
__dirname,
|
||||
`../../models/${request.params.modelName}`,
|
||||
),
|
||||
certificates: {
|
||||
wwdr: certificates.wwdr,
|
||||
signerCert: certificates.signerCert,
|
||||
signerKey: certificates.signerKey,
|
||||
signerKeyPassphrase: certificates.signerKeyPassphrase,
|
||||
},
|
||||
},
|
||||
request.body || request.params || request.query || {},
|
||||
);
|
||||
|
||||
if (request.query.alt === "true") {
|
||||
// After this, pass.props["barcodes"] will have support for all the formats
|
||||
pass.setBarcodes("Thank you for using this package <3");
|
||||
|
||||
console.log(
|
||||
"Barcodes support is autocompleted:",
|
||||
pass.props["barcodes"],
|
||||
);
|
||||
} else {
|
||||
// After this, pass.props["barcodes"] will have support for just two of three
|
||||
// of the passed format (the valid ones);
|
||||
|
||||
pass.setBarcodes(
|
||||
{
|
||||
message: "Thank you for using this package <3",
|
||||
format: "PKBarcodeFormatCode128",
|
||||
},
|
||||
{
|
||||
message: "Thank you for using this package <3",
|
||||
format: "PKBarcodeFormatPDF417",
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const stream = pass.getAsStream();
|
||||
|
||||
response.set({
|
||||
"Content-type": pass.mimeType,
|
||||
"Content-disposition": `attachment; filename=${passName}.pkpass`,
|
||||
});
|
||||
|
||||
stream.pipe(response);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
|
||||
response.set({
|
||||
"Content-type": "text/html",
|
||||
});
|
||||
|
||||
response.send(err.message);
|
||||
}
|
||||
});
|
||||
82
examples/self-hosted/src/setExpirationDate.ts
Normal file
82
examples/self-hosted/src/setExpirationDate.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* .expiration() method and voided prop example
|
||||
* To check if a ticket is void, look at the barcode;
|
||||
* If it is grayed, the ticket is voided. May not be showed on macOS.
|
||||
*
|
||||
* To check if a ticket has an expiration date, you'll
|
||||
* have to wait two minutes.
|
||||
*/
|
||||
|
||||
import { app } from "./webserver";
|
||||
import { getCertificates } from "./shared";
|
||||
import path from "path";
|
||||
import { PKPass } from "passkit-generator";
|
||||
|
||||
app.route("/expirationDate/:modelName").get(async (request, response) => {
|
||||
if (!request.query.fn) {
|
||||
response.send(
|
||||
"<a href='?fn=void'>Generate a voided pass.</a><br><a href='?fn=expiration'>Generate a pass with expiration date</a>",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const certificates = await getCertificates();
|
||||
|
||||
const passName =
|
||||
request.params.modelName +
|
||||
"_" +
|
||||
new Date().toISOString().split("T")[0].replace(/-/gi, "");
|
||||
|
||||
try {
|
||||
const pass = await PKPass.from(
|
||||
{
|
||||
model: path.resolve(
|
||||
__dirname,
|
||||
`../../models/${request.params.modelName}`,
|
||||
),
|
||||
certificates: {
|
||||
wwdr: certificates.wwdr,
|
||||
signerCert: certificates.signerCert,
|
||||
signerKey: certificates.signerKey,
|
||||
signerKeyPassphrase: certificates.signerKeyPassphrase,
|
||||
},
|
||||
},
|
||||
Object.assign(
|
||||
{
|
||||
voided: request.query.fn === "void",
|
||||
},
|
||||
{ ...(request.body || request.params || request.query || {}) },
|
||||
),
|
||||
);
|
||||
|
||||
if (request.query.fn === "expiration") {
|
||||
// 2 minutes later...
|
||||
const d = new Date();
|
||||
d.setMinutes(d.getMinutes() + 2);
|
||||
|
||||
// setting the expiration
|
||||
pass.setExpirationDate(d);
|
||||
console.log(
|
||||
"EXPIRATION DATE EXPECTED:",
|
||||
pass.props["expirationDate"],
|
||||
);
|
||||
}
|
||||
|
||||
const stream = pass.getAsStream();
|
||||
|
||||
response.set({
|
||||
"Content-type": pass.mimeType,
|
||||
"Content-disposition": `attachment; filename=${passName}.pkpass`,
|
||||
});
|
||||
|
||||
stream.pipe(response);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
|
||||
response.set({
|
||||
"Content-type": "text/html",
|
||||
});
|
||||
|
||||
response.send(err.message);
|
||||
}
|
||||
});
|
||||
41
examples/self-hosted/src/shared.ts
Normal file
41
examples/self-hosted/src/shared.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { promises as fs } from "fs";
|
||||
import path from "path";
|
||||
|
||||
const certificatesCache: Partial<{
|
||||
signerCert: Buffer;
|
||||
signerKey: Buffer;
|
||||
wwdr: Buffer;
|
||||
signerKeyPassphrase: string;
|
||||
}> = {};
|
||||
|
||||
export async function getCertificates(): Promise<typeof certificatesCache> {
|
||||
if (Object.keys(certificatesCache).length) {
|
||||
return certificatesCache;
|
||||
}
|
||||
|
||||
const [signerCert, signerKey, wwdr, signerKeyPassphrase] =
|
||||
await Promise.all([
|
||||
fs.readFile(
|
||||
path.resolve(__dirname, "../../../certificates/signerCert.pem"),
|
||||
"utf-8",
|
||||
),
|
||||
fs.readFile(
|
||||
path.resolve(__dirname, "../../../certificates/signerKey.pem"),
|
||||
"utf-8",
|
||||
),
|
||||
fs.readFile(
|
||||
path.resolve(__dirname, "../../../certificates/WWDR.pem"),
|
||||
"utf-8",
|
||||
),
|
||||
Promise.resolve("123456"),
|
||||
]);
|
||||
|
||||
Object.assign(certificatesCache, {
|
||||
signerCert,
|
||||
signerKey,
|
||||
wwdr,
|
||||
signerKeyPassphrase,
|
||||
});
|
||||
|
||||
return certificatesCache;
|
||||
}
|
||||
14
examples/self-hosted/src/webserver.ts
Normal file
14
examples/self-hosted/src/webserver.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Generic webserver instance for the examples
|
||||
* @Author Alexander P. Cerutti
|
||||
* Requires express to run
|
||||
*/
|
||||
|
||||
import express from "express";
|
||||
export const app = express();
|
||||
|
||||
app.use(express.json());
|
||||
|
||||
app.listen(8080, "0.0.0.0", () => {
|
||||
console.log("Webserver started.");
|
||||
});
|
||||
11
examples/self-hosted/tsconfig.json
Normal file
11
examples/self-hosted/tsconfig.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"module": "CommonJS",
|
||||
"outDir": "build",
|
||||
"moduleResolution": "node",
|
||||
"sourceMap": true
|
||||
},
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
Reference in New Issue
Block a user