mirror of
https://github.com/marcogll/passkit-generator.git
synced 2026-03-15 10:25:16 +00:00
Added prettier to project
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -3,7 +3,8 @@ node_modules
|
||||
passModels/
|
||||
certificates/
|
||||
*.code-workspace
|
||||
.vscode/
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
*.js
|
||||
lib/
|
||||
examples/build
|
||||
|
||||
6
.prettierrc
Normal file
6
.prettierrc
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"bracketSpacing": true,
|
||||
"trailingComma": "all",
|
||||
"tabWidth": 4,
|
||||
"useTabs": true
|
||||
}
|
||||
7
.vscode/settings.json
vendored
Normal file
7
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"editor.tabSize": 4,
|
||||
"editor.formatOnSave": true,
|
||||
"editor.insertSpaces": false,
|
||||
"editor.smoothScrolling": true,
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
This is examples folder. These examples are used to test new features and as sample showcases.
|
||||
|
||||
Each example is linked to webserver.js, which *requires* express.js to run.
|
||||
Each example is linked to webserver.js, which _requires_ express.js to run.
|
||||
Express.js has been inserted as "example package" dipendency.
|
||||
|
||||
```sh
|
||||
@@ -18,6 +18,7 @@ To make them work, you'll have to edit both certificates and model path.
|
||||
|
||||
Visit [http://localhost:8080/gen/examplePass](http://localhost:8080/gen/examplePass) to get the pass. Replace "examplePass" with the pass name in models folder.
|
||||
Please note that `field.js` example will force you to download `exampleBooking.pass`, no matter what.
|
||||
___
|
||||
|
||||
---
|
||||
|
||||
Every contribution is really appreciated. ❤️ Thank you!
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import genRoute, { app } from "./webserver";
|
||||
import { createPass, createAbstractModel, AbstractModel } from "passkit-generator";
|
||||
import {
|
||||
createPass,
|
||||
createAbstractModel,
|
||||
AbstractModel,
|
||||
} from "passkit-generator";
|
||||
|
||||
let abstractModel: AbstractModel;
|
||||
|
||||
@@ -11,148 +15,182 @@ let abstractModel: AbstractModel;
|
||||
signerCert: "../certificates/signerCert.pem",
|
||||
signerKey: {
|
||||
keyFile: "../certificates/signerKey.pem",
|
||||
passphrase: "123456"
|
||||
}
|
||||
passphrase: "123456",
|
||||
},
|
||||
},
|
||||
// overrides: request.body || request.params || request.query,
|
||||
});
|
||||
})();
|
||||
|
||||
genRoute.all(async function manageRequest(request, response) {
|
||||
const passName = request.params.modelName + "_" + (new Date()).toISOString().split('T')[0].replace(/-/ig, "");
|
||||
const passName =
|
||||
request.params.modelName +
|
||||
"_" +
|
||||
new Date().toISOString().split("T")[0].replace(/-/gi, "");
|
||||
|
||||
try {
|
||||
const pass = await createPass(abstractModel);
|
||||
|
||||
pass.transitType = "PKTransitTypeAir";
|
||||
|
||||
pass.headerFields.push({
|
||||
"key": "header1",
|
||||
"label": "Data",
|
||||
"value": "25 mag",
|
||||
"textAlignment": "PKTextAlignmentCenter"
|
||||
}, {
|
||||
"key": "header2",
|
||||
"label": "Volo",
|
||||
"value": "EZY997",
|
||||
"textAlignment": "PKTextAlignmentCenter"
|
||||
});
|
||||
pass.headerFields.push(
|
||||
{
|
||||
key: "header1",
|
||||
label: "Data",
|
||||
value: "25 mag",
|
||||
textAlignment: "PKTextAlignmentCenter",
|
||||
},
|
||||
{
|
||||
key: "header2",
|
||||
label: "Volo",
|
||||
value: "EZY997",
|
||||
textAlignment: "PKTextAlignmentCenter",
|
||||
},
|
||||
);
|
||||
|
||||
pass.primaryFields.push({
|
||||
pass.primaryFields.push(
|
||||
{
|
||||
key: "IATA-source",
|
||||
value: "NAP",
|
||||
label: "Napoli",
|
||||
textAlignment: "PKTextAlignmentLeft"
|
||||
}, {
|
||||
textAlignment: "PKTextAlignmentLeft",
|
||||
},
|
||||
{
|
||||
key: "IATA-destination",
|
||||
value: "VCE",
|
||||
label: "Venezia Marco Polo",
|
||||
textAlignment: "PKTextAlignmentRight"
|
||||
});
|
||||
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.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.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"
|
||||
});
|
||||
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.generate();
|
||||
response.set({
|
||||
"Content-type": "application/vnd.apple.pkpass",
|
||||
"Content-disposition": `attachment; filename=${passName}.pkpass`
|
||||
"Content-disposition": `attachment; filename=${passName}.pkpass`,
|
||||
});
|
||||
|
||||
stream.pipe(response);
|
||||
} catch(err) {
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
|
||||
response.set({
|
||||
"Content-type": "text/html"
|
||||
"Content-type": "text/html",
|
||||
});
|
||||
|
||||
response.send(err.message);
|
||||
|
||||
@@ -11,12 +11,14 @@ import fetch from "node-fetch";
|
||||
import { createPass } from "passkit-generator";
|
||||
|
||||
app.all(async function manageRequest(request, response) {
|
||||
let passName = request.params.modelName + "_" + (new Date()).toISOString().split('T')[0].replace(/-/ig, "");
|
||||
let passName =
|
||||
request.params.modelName +
|
||||
"_" +
|
||||
new Date().toISOString().split("T")[0].replace(/-/gi, "");
|
||||
|
||||
const avatar = await (
|
||||
fetch("https://s.gravatar.com/avatar/83cd11399b7ea79977bc302f3931ee52?size=32&default=retro")
|
||||
.then(res => res.buffer())
|
||||
);
|
||||
const avatar = await fetch(
|
||||
"https://s.gravatar.com/avatar/83cd11399b7ea79977bc302f3931ee52?size=32&default=retro",
|
||||
).then((res) => res.buffer());
|
||||
|
||||
const passConfig = {
|
||||
model: `./models/${request.params.modelName}`,
|
||||
@@ -25,8 +27,8 @@ app.all(async function manageRequest(request, response) {
|
||||
signerCert: "../certificates/signerCert.pem",
|
||||
signerKey: {
|
||||
keyFile: "../certificates/signerKey.pem",
|
||||
passphrase: "123456"
|
||||
}
|
||||
passphrase: "123456",
|
||||
},
|
||||
},
|
||||
overrides: request.body || request.params || request.query,
|
||||
};
|
||||
@@ -44,7 +46,7 @@ app.all(async function manageRequest(request, response) {
|
||||
|
||||
response.set({
|
||||
"Content-type": "application/vnd.apple.pkpass",
|
||||
"Content-disposition": `attachment; filename=${passName}.pkpass`
|
||||
"Content-disposition": `attachment; filename=${passName}.pkpass`,
|
||||
});
|
||||
|
||||
stream.pipe(response);
|
||||
|
||||
@@ -12,7 +12,10 @@ import app from "./webserver";
|
||||
import { createPass } from "passkit-generator";
|
||||
|
||||
app.all(async function manageRequest(request, response) {
|
||||
const passName = request.params.modelName + "_" + (new Date()).toISOString().split('T')[0].replace(/-/ig, "");
|
||||
const passName =
|
||||
request.params.modelName +
|
||||
"_" +
|
||||
new Date().toISOString().split("T")[0].replace(/-/gi, "");
|
||||
|
||||
try {
|
||||
const pass = await createPass({
|
||||
@@ -22,8 +25,8 @@ app.all(async function manageRequest(request, response) {
|
||||
signerCert: "../certificates/signerCert.pem",
|
||||
signerKey: {
|
||||
keyFile: "../certificates/signerKey.pem",
|
||||
passphrase: "123456"
|
||||
}
|
||||
passphrase: "123456",
|
||||
},
|
||||
},
|
||||
overrides: request.body || request.params || request.query,
|
||||
});
|
||||
@@ -37,17 +40,21 @@ app.all(async function manageRequest(request, response) {
|
||||
// After this, pass.props["barcodes"] will have support for just two of three
|
||||
// of the passed format (the valid ones);
|
||||
|
||||
pass.barcodes({
|
||||
pass.barcodes(
|
||||
{
|
||||
message: "Thank you for using this package <3",
|
||||
format: "PKBarcodeFormatCode128"
|
||||
}, {
|
||||
format: "PKBarcodeFormatCode128",
|
||||
},
|
||||
{
|
||||
message: "Thank you for using this package <3",
|
||||
format: "PKBarcodeFormatPDF417"
|
||||
}, {
|
||||
format: "PKBarcodeFormatPDF417",
|
||||
},
|
||||
{
|
||||
message: "Thank you for using this package <3",
|
||||
// @ts-expect-error
|
||||
format: "PKBarcodeFormatMock44617"
|
||||
});
|
||||
format: "PKBarcodeFormatMock44617",
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// You can change the format chosen for barcode prop support by calling .barcode()
|
||||
@@ -57,12 +64,15 @@ app.all(async function manageRequest(request, response) {
|
||||
pass.barcode("PKBarcodeFormatPDF417");
|
||||
|
||||
console.log("Barcode property is now:", pass.props["barcode"]);
|
||||
console.log("Barcodes support is autocompleted:", pass.props["barcodes"]);
|
||||
console.log(
|
||||
"Barcodes support is autocompleted:",
|
||||
pass.props["barcodes"],
|
||||
);
|
||||
|
||||
const stream = pass.generate();
|
||||
response.set({
|
||||
"Content-type": "application/vnd.apple.pkpass",
|
||||
"Content-disposition": `attachment; filename=${passName}.pkpass`
|
||||
"Content-disposition": `attachment; filename=${passName}.pkpass`,
|
||||
});
|
||||
|
||||
stream.pipe(response);
|
||||
|
||||
@@ -13,11 +13,16 @@ import { createPass } from "passkit-generator";
|
||||
|
||||
app.all(async function manageRequest(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>");
|
||||
response.send(
|
||||
"<a href='?fn=void'>Generate a voided pass.</a><br><a href='?fn=expiration'>Generate a pass with expiration date</a>",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let passName = request.params.modelName + "_" + (new Date()).toISOString().split('T')[0].replace(/-/ig, "");
|
||||
let passName =
|
||||
request.params.modelName +
|
||||
"_" +
|
||||
new Date().toISOString().split("T")[0].replace(/-/gi, "");
|
||||
|
||||
try {
|
||||
let pass = await createPass({
|
||||
@@ -27,8 +32,8 @@ app.all(async function manageRequest(request, response) {
|
||||
signerCert: "../certificates/signerCert.pem",
|
||||
signerKey: {
|
||||
keyFile: "../certificates/signerKey.pem",
|
||||
passphrase: "123456"
|
||||
}
|
||||
passphrase: "123456",
|
||||
},
|
||||
},
|
||||
overrides: request.body || request.params || request.query,
|
||||
});
|
||||
@@ -47,7 +52,7 @@ app.all(async function manageRequest(request, response) {
|
||||
const stream = pass.generate();
|
||||
response.set({
|
||||
"Content-type": "application/vnd.apple.pkpass",
|
||||
"Content-disposition": `attachment; filename=${passName}.pkpass`
|
||||
"Content-disposition": `attachment; filename=${passName}.pkpass`,
|
||||
});
|
||||
|
||||
stream.pipe(response);
|
||||
|
||||
@@ -13,7 +13,10 @@ import app from "./webserver";
|
||||
import { createPass } from "passkit-generator";
|
||||
|
||||
app.all(async function manageRequest(request, response) {
|
||||
let passName = "exampleBooking" + "_" + (new Date()).toISOString().split('T')[0].replace(/-/ig, "");
|
||||
let passName =
|
||||
"exampleBooking" +
|
||||
"_" +
|
||||
new Date().toISOString().split("T")[0].replace(/-/gi, "");
|
||||
try {
|
||||
let pass = await createPass({
|
||||
model: `./models/exampleBooking`,
|
||||
@@ -22,133 +25,164 @@ app.all(async function manageRequest(request, response) {
|
||||
signerCert: "../certificates/signerCert.pem",
|
||||
signerKey: {
|
||||
keyFile: "../certificates/signerKey.pem",
|
||||
passphrase: "123456"
|
||||
}
|
||||
passphrase: "123456",
|
||||
},
|
||||
},
|
||||
overrides: 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.headerFields.push(
|
||||
{
|
||||
key: "header1",
|
||||
label: "Data",
|
||||
value: "25 mag",
|
||||
textAlignment: "PKTextAlignmentCenter",
|
||||
},
|
||||
{
|
||||
key: "header2",
|
||||
label: "Volo",
|
||||
value: "EZY997",
|
||||
textAlignment: "PKTextAlignmentCenter",
|
||||
},
|
||||
);
|
||||
|
||||
pass.primaryFields.push({
|
||||
pass.primaryFields.push(
|
||||
{
|
||||
key: "IATA-source",
|
||||
value: "NAP",
|
||||
label: "Napoli",
|
||||
textAlignment: "PKTextAlignmentLeft"
|
||||
}, {
|
||||
textAlignment: "PKTextAlignmentLeft",
|
||||
},
|
||||
{
|
||||
key: "IATA-destination",
|
||||
value: "VCE",
|
||||
label: "Venezia Marco Polo",
|
||||
textAlignment: "PKTextAlignmentRight"
|
||||
});
|
||||
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.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.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"
|
||||
});
|
||||
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.generate();
|
||||
response.set({
|
||||
"Content-type": "application/vnd.apple.pkpass",
|
||||
"Content-disposition": `attachment; filename=${passName}.pkpass`
|
||||
"Content-disposition": `attachment; filename=${passName}.pkpass`,
|
||||
});
|
||||
|
||||
stream.pipe(response);
|
||||
@@ -156,7 +190,7 @@ app.all(async function manageRequest(request, response) {
|
||||
console.log(err);
|
||||
|
||||
response.set({
|
||||
"Content-type": "text/html"
|
||||
"Content-type": "text/html",
|
||||
});
|
||||
|
||||
response.send(err.message);
|
||||
|
||||
@@ -8,7 +8,10 @@ import app from "./webserver";
|
||||
import { createPass } from "passkit-generator";
|
||||
|
||||
app.all(async function manageRequest(request, response) {
|
||||
const passName = request.params.modelName + "_" + (new Date()).toISOString().split('T')[0].replace(/-/ig, "");
|
||||
const passName =
|
||||
request.params.modelName +
|
||||
"_" +
|
||||
new Date().toISOString().split("T")[0].replace(/-/gi, "");
|
||||
|
||||
try {
|
||||
const pass = await createPass({
|
||||
@@ -18,10 +21,10 @@ app.all(async function manageRequest(request, response) {
|
||||
signerCert: "../certificates/signerCert.pem",
|
||||
signerKey: {
|
||||
keyFile: "../certificates/signerKey.pem",
|
||||
passphrase: "123456"
|
||||
}
|
||||
passphrase: "123456",
|
||||
},
|
||||
overrides: request.body || request.params || request.query
|
||||
},
|
||||
overrides: request.body || request.params || request.query,
|
||||
});
|
||||
|
||||
// For each language you include, an .lproj folder in pass bundle
|
||||
@@ -38,30 +41,33 @@ app.all(async function manageRequest(request, response) {
|
||||
|
||||
// Italian, already has an .lproj which gets included
|
||||
pass.localize("it", {
|
||||
"EVENT": "Evento",
|
||||
"LOCATION": "Dove"
|
||||
EVENT: "Evento",
|
||||
LOCATION: "Dove",
|
||||
});
|
||||
|
||||
// German, doesn't, so is created
|
||||
pass.localize("de", {
|
||||
"EVENT": "Ereignis",
|
||||
"LOCATION": "Ort"
|
||||
EVENT: "Ereignis",
|
||||
LOCATION: "Ort",
|
||||
});
|
||||
|
||||
// This language does not exist but is still added as .lproj folder
|
||||
pass.localize("zu", {});
|
||||
|
||||
// @ts-ignore - ignoring for logging purposes. Do not replicate
|
||||
console.log("Added languages", Object.keys(pass.l10nTranslations).join(", "))
|
||||
console.log(
|
||||
"Added languages",
|
||||
Object.keys(pass.l10nTranslations).join(", "),
|
||||
);
|
||||
|
||||
const stream = pass.generate();
|
||||
response.set({
|
||||
"Content-type": "application/vnd.apple.pkpass",
|
||||
"Content-disposition": `attachment; filename=${passName}.pkpass`
|
||||
"Content-disposition": `attachment; filename=${passName}.pkpass`,
|
||||
});
|
||||
|
||||
stream.pipe(response);
|
||||
} catch(err) {
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
|
||||
response.set({
|
||||
|
||||
@@ -16,11 +16,13 @@
|
||||
"latitude": 37.33182
|
||||
}
|
||||
],
|
||||
"barcodes": [{
|
||||
"barcodes": [
|
||||
{
|
||||
"message": "123456789",
|
||||
"format": "PKBarcodeFormatQR",
|
||||
"messageEncoding": "iso-8859-1"
|
||||
}],
|
||||
}
|
||||
],
|
||||
"barcode": {
|
||||
"message": "123456789",
|
||||
"format": "PKBarcodeFormatQR",
|
||||
|
||||
@@ -3,7 +3,5 @@
|
||||
"compilerOptions": {
|
||||
"outDir": "build"
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ export const app = express();
|
||||
|
||||
app.use(express.json());
|
||||
|
||||
app.listen(8080, "0.0.0.0", function(request, response) {
|
||||
app.listen(8080, "0.0.0.0", function (request, response) {
|
||||
console.log("Webserver started.");
|
||||
});
|
||||
|
||||
@@ -17,10 +17,11 @@ app.all("/", function (request, response) {
|
||||
response.redirect("/gen/");
|
||||
});
|
||||
|
||||
app.route("/gen")
|
||||
.all((req, res) => {
|
||||
app.route("/gen").all((req, res) => {
|
||||
res.set("Content-Type", "text/html");
|
||||
res.send("Cannot generate a pass. Specify a modelName in the url to continue. <br/>Usage: /gen/<i>modelName</i>")
|
||||
});
|
||||
res.send(
|
||||
"Cannot generate a pass. Specify a modelName in the url to continue. <br/>Usage: /gen/<i>modelName</i>",
|
||||
);
|
||||
});
|
||||
|
||||
export default app.route("/gen/:modelName");
|
||||
|
||||
6
package-lock.json
generated
6
package-lock.json
generated
@@ -209,6 +209,12 @@
|
||||
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
|
||||
"dev": true
|
||||
},
|
||||
"prettier": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.2.1.tgz",
|
||||
"integrity": "sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==",
|
||||
"dev": true
|
||||
},
|
||||
"rimraf": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
"@types/node-forge": "^0.9.7",
|
||||
"@types/yazl": "^2.4.2",
|
||||
"jasmine": "^3.6.4",
|
||||
"prettier": "^2.2.1",
|
||||
"rimraf": "^3.0.2",
|
||||
"typescript": "^4.1.3"
|
||||
},
|
||||
|
||||
149
spec/factory.ts
149
spec/factory.ts
@@ -6,7 +6,7 @@ import * as path from "path";
|
||||
describe("createPass", () => {
|
||||
it("should throw if first argument is not provided", async () => {
|
||||
await expectAsync(createPass(undefined)).toBeRejectedWithError(
|
||||
formatMessage("CP_NO_OPTS")
|
||||
formatMessage("CP_NO_OPTS"),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -27,71 +27,144 @@ describe("createPass", () => {
|
||||
|
||||
if (certificatesPath) {
|
||||
it("should return a pass instance", async () => {
|
||||
await expectAsync(createPass({
|
||||
model: path.resolve(__dirname, "../examples/models/exampleBooking.pass"),
|
||||
await expectAsync(
|
||||
createPass({
|
||||
model: path.resolve(
|
||||
__dirname,
|
||||
"../examples/models/exampleBooking.pass",
|
||||
),
|
||||
certificates: {
|
||||
signerCert: path.resolve(__dirname, certificatesPath, "./signerCert.pem"),
|
||||
signerCert: path.resolve(
|
||||
__dirname,
|
||||
certificatesPath,
|
||||
"./signerCert.pem",
|
||||
),
|
||||
signerKey: {
|
||||
keyFile: path.resolve(__dirname, certificatesPath, "./signerKey.pem"),
|
||||
passphrase: "password1234"
|
||||
keyFile: path.resolve(
|
||||
__dirname,
|
||||
certificatesPath,
|
||||
"./signerKey.pem",
|
||||
),
|
||||
passphrase: "password1234",
|
||||
},
|
||||
wwdr: path.resolve(__dirname, certificatesPath, "./WWDR.pem"),
|
||||
}
|
||||
})).toBeResolved();
|
||||
wwdr: path.resolve(
|
||||
__dirname,
|
||||
certificatesPath,
|
||||
"./WWDR.pem",
|
||||
),
|
||||
},
|
||||
}),
|
||||
).toBeResolved();
|
||||
});
|
||||
|
||||
describe("Model validation", () => {
|
||||
it("Should reject with non valid model", async () => {
|
||||
await expectAsync(createPass({
|
||||
await expectAsync(
|
||||
createPass({
|
||||
// @ts-expect-error
|
||||
model: 0,
|
||||
certificates: {
|
||||
signerCert: path.resolve(__dirname, certificatesPath, "./signerCert.pem"),
|
||||
signerCert: path.resolve(
|
||||
__dirname,
|
||||
certificatesPath,
|
||||
"./signerCert.pem",
|
||||
),
|
||||
signerKey: {
|
||||
keyFile: path.resolve(__dirname, certificatesPath, "./signerKey.pem"),
|
||||
passphrase: "password1234"
|
||||
keyFile: path.resolve(
|
||||
__dirname,
|
||||
certificatesPath,
|
||||
"./signerKey.pem",
|
||||
),
|
||||
passphrase: "password1234",
|
||||
},
|
||||
wwdr: path.resolve(__dirname, certificatesPath, "./WWDR.pem"),
|
||||
}
|
||||
})).toBeRejected();
|
||||
wwdr: path.resolve(
|
||||
__dirname,
|
||||
certificatesPath,
|
||||
"./WWDR.pem",
|
||||
),
|
||||
},
|
||||
}),
|
||||
).toBeRejected();
|
||||
|
||||
await expectAsync(createPass({
|
||||
await expectAsync(
|
||||
createPass({
|
||||
model: undefined,
|
||||
certificates: {
|
||||
signerCert: path.resolve(__dirname, certificatesPath, "./signerCert.pem"),
|
||||
signerCert: path.resolve(
|
||||
__dirname,
|
||||
certificatesPath,
|
||||
"./signerCert.pem",
|
||||
),
|
||||
signerKey: {
|
||||
keyFile: path.resolve(__dirname, certificatesPath, "./signerKey.pem"),
|
||||
passphrase: "password1234"
|
||||
keyFile: path.resolve(
|
||||
__dirname,
|
||||
certificatesPath,
|
||||
"./signerKey.pem",
|
||||
),
|
||||
passphrase: "password1234",
|
||||
},
|
||||
wwdr: path.resolve(__dirname, certificatesPath, "./WWDR.pem"),
|
||||
}
|
||||
})).toBeRejected();
|
||||
wwdr: path.resolve(
|
||||
__dirname,
|
||||
certificatesPath,
|
||||
"./WWDR.pem",
|
||||
),
|
||||
},
|
||||
}),
|
||||
).toBeRejected();
|
||||
|
||||
await expectAsync(createPass({
|
||||
await expectAsync(
|
||||
createPass({
|
||||
model: null,
|
||||
certificates: {
|
||||
signerCert: path.resolve(__dirname, certificatesPath, "./signerCert.pem"),
|
||||
signerCert: path.resolve(
|
||||
__dirname,
|
||||
certificatesPath,
|
||||
"./signerCert.pem",
|
||||
),
|
||||
signerKey: {
|
||||
keyFile: path.resolve(__dirname, certificatesPath, "./signerKey.pem"),
|
||||
passphrase: "password1234"
|
||||
keyFile: path.resolve(
|
||||
__dirname,
|
||||
certificatesPath,
|
||||
"./signerKey.pem",
|
||||
),
|
||||
passphrase: "password1234",
|
||||
},
|
||||
wwdr: path.resolve(__dirname, certificatesPath, "./WWDR.pem"),
|
||||
}
|
||||
})).toBeRejected();
|
||||
wwdr: path.resolve(
|
||||
__dirname,
|
||||
certificatesPath,
|
||||
"./WWDR.pem",
|
||||
),
|
||||
},
|
||||
}),
|
||||
).toBeRejected();
|
||||
|
||||
await expectAsync(createPass({
|
||||
await expectAsync(
|
||||
createPass({
|
||||
model: {},
|
||||
certificates: {
|
||||
signerCert: path.resolve(__dirname, certificatesPath, "./signerCert.pem"),
|
||||
signerCert: path.resolve(
|
||||
__dirname,
|
||||
certificatesPath,
|
||||
"./signerCert.pem",
|
||||
),
|
||||
signerKey: {
|
||||
keyFile: path.resolve(__dirname, certificatesPath, "./signerKey.pem"),
|
||||
passphrase: "password1234"
|
||||
keyFile: path.resolve(
|
||||
__dirname,
|
||||
certificatesPath,
|
||||
"./signerKey.pem",
|
||||
),
|
||||
passphrase: "password1234",
|
||||
},
|
||||
wwdr: path.resolve(__dirname, certificatesPath, "./WWDR.pem"),
|
||||
}
|
||||
})).toBeRejected();
|
||||
wwdr: path.resolve(
|
||||
__dirname,
|
||||
certificatesPath,
|
||||
"./WWDR.pem",
|
||||
),
|
||||
},
|
||||
}),
|
||||
).toBeRejected();
|
||||
});
|
||||
});
|
||||
}
|
||||
} catch (err) { }
|
||||
} catch (err) {}
|
||||
});
|
||||
|
||||
150
spec/index.ts
150
spec/index.ts
@@ -9,16 +9,25 @@ describe("Passkit-generator", function () {
|
||||
let pass: Pass;
|
||||
beforeEach(async () => {
|
||||
pass = await createPass({
|
||||
model: path.resolve(__dirname, "../examples/models/examplePass.pass"),
|
||||
model: path.resolve(
|
||||
__dirname,
|
||||
"../examples/models/examplePass.pass",
|
||||
),
|
||||
certificates: {
|
||||
wwdr: path.resolve(__dirname, "../certificates/WWDR.pem"),
|
||||
signerCert: path.resolve(__dirname, "../certificates/signerCert.pem"),
|
||||
signerCert: path.resolve(
|
||||
__dirname,
|
||||
"../certificates/signerCert.pem",
|
||||
),
|
||||
signerKey: {
|
||||
keyFile: path.resolve(__dirname, "../certificates/signerKey.pem"),
|
||||
passphrase: "123456"
|
||||
}
|
||||
keyFile: path.resolve(
|
||||
__dirname,
|
||||
"../certificates/signerKey.pem",
|
||||
),
|
||||
passphrase: "123456",
|
||||
},
|
||||
overrides: {}
|
||||
},
|
||||
overrides: {},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -48,7 +57,7 @@ describe("Passkit-generator", function () {
|
||||
|
||||
it("Will apply changes if a second object argument with translations is passed", () => {
|
||||
pass.localize("it", {
|
||||
"Test": "Prova"
|
||||
Test: "Prova",
|
||||
});
|
||||
|
||||
expect(typeof pass["l10nTranslations"]["it"]).toBe("object");
|
||||
@@ -99,16 +108,21 @@ describe("Passkit-generator", function () {
|
||||
describe("locations()", () => {
|
||||
it("Won't apply changes if invalid location objects are passed", () => {
|
||||
const props = pass.props["locations"] || [];
|
||||
const oldAmountOfLocations = props && props.length || 0;
|
||||
const oldAmountOfLocations = (props && props.length) || 0;
|
||||
|
||||
pass.locations({
|
||||
pass.locations(
|
||||
{
|
||||
// @ts-expect-error
|
||||
"ibrupofene": "no",
|
||||
"longitude": 0.00000000
|
||||
}, ...props);
|
||||
ibrupofene: "no",
|
||||
longitude: 0.0,
|
||||
},
|
||||
...props,
|
||||
);
|
||||
|
||||
if (oldAmountOfLocations) {
|
||||
expect(pass.props["locations"].length).toBe(oldAmountOfLocations);
|
||||
expect(pass.props["locations"].length).toBe(
|
||||
oldAmountOfLocations,
|
||||
);
|
||||
} else {
|
||||
expect(pass.props["locations"]).toBe(undefined);
|
||||
}
|
||||
@@ -116,33 +130,42 @@ describe("Passkit-generator", function () {
|
||||
|
||||
it("Will filter out invalid location objects", () => {
|
||||
const props = pass.props["locations"] || [];
|
||||
const oldAmountOfLocations = props && props.length || 0;
|
||||
const oldAmountOfLocations = (props && props.length) || 0;
|
||||
|
||||
pass.locations({
|
||||
pass.locations(
|
||||
{
|
||||
// @ts-expect-error
|
||||
"ibrupofene": "no",
|
||||
"longitude": 0.00000000
|
||||
}, {
|
||||
"longitude": 4.42634523,
|
||||
"latitude": 5.344233323352
|
||||
}, ...(pass.props["locations"] || []));
|
||||
ibrupofene: "no",
|
||||
longitude: 0.0,
|
||||
},
|
||||
{
|
||||
longitude: 4.42634523,
|
||||
latitude: 5.344233323352,
|
||||
},
|
||||
...(pass.props["locations"] || []),
|
||||
);
|
||||
|
||||
expect(pass.props["locations"].length).toBe((oldAmountOfLocations || 0) + 1);
|
||||
expect(pass.props["locations"].length).toBe(
|
||||
(oldAmountOfLocations || 0) + 1,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Beacons()", () => {
|
||||
it("Won't apply changes if invalid beacon objects are passed", () => {
|
||||
const props = pass.props["beacons"] || [];
|
||||
const oldAmountOfBeacons = props && props.length || 0;
|
||||
const oldAmountOfBeacons = (props && props.length) || 0;
|
||||
|
||||
pass.beacons({
|
||||
pass.beacons(
|
||||
{
|
||||
// @ts-expect-error
|
||||
"ibrupofene": "no",
|
||||
"major": 55,
|
||||
"minor": 0,
|
||||
"proximityUUID": "2707c5f4-deb9-48ff-b760-671bc885b6a7"
|
||||
}, ...props);
|
||||
ibrupofene: "no",
|
||||
major: 55,
|
||||
minor: 0,
|
||||
proximityUUID: "2707c5f4-deb9-48ff-b760-671bc885b6a7",
|
||||
},
|
||||
...props,
|
||||
);
|
||||
|
||||
if (oldAmountOfBeacons) {
|
||||
expect(pass.props["beacons"].length).toBe(oldAmountOfBeacons);
|
||||
@@ -153,20 +176,23 @@ describe("Passkit-generator", function () {
|
||||
|
||||
it("Will filter out invalid beacons objects", () => {
|
||||
const props = pass.props["beacons"] || [];
|
||||
const oldAmountOfBeacons = props && props.length || 0;
|
||||
const oldAmountOfBeacons = (props && props.length) || 0;
|
||||
|
||||
pass.beacons({
|
||||
"major": 55,
|
||||
"minor": 0,
|
||||
"proximityUUID": "59da0f96-3fb5-43aa-9028-2bc796c3d0c5"
|
||||
}, {
|
||||
"major": 55,
|
||||
"minor": 0,
|
||||
"proximityUUID": "fdcbbf48-a4ae-4ffb-9200-f8a373c5c18e",
|
||||
pass.beacons(
|
||||
{
|
||||
major: 55,
|
||||
minor: 0,
|
||||
proximityUUID: "59da0f96-3fb5-43aa-9028-2bc796c3d0c5",
|
||||
},
|
||||
{
|
||||
major: 55,
|
||||
minor: 0,
|
||||
proximityUUID: "fdcbbf48-a4ae-4ffb-9200-f8a373c5c18e",
|
||||
// @ts-expect-error
|
||||
"animal": "Monkey"
|
||||
}, ...props);
|
||||
|
||||
animal: "Monkey",
|
||||
},
|
||||
...props,
|
||||
);
|
||||
|
||||
expect(pass.props["beacons"].length).toBe(oldAmountOfBeacons + 1);
|
||||
});
|
||||
@@ -175,7 +201,7 @@ describe("Passkit-generator", function () {
|
||||
describe("barcodes()", () => {
|
||||
it("Won't apply changes if no data is passed", () => {
|
||||
const props = pass.props["barcodes"] || [];
|
||||
const oldAmountOfBarcodes = props && props.length || 0;
|
||||
const oldAmountOfBarcodes = (props && props.length) || 0;
|
||||
|
||||
pass.barcodes();
|
||||
expect(pass.props["barcodes"].length).toBe(oldAmountOfBarcodes);
|
||||
@@ -183,7 +209,7 @@ describe("Passkit-generator", function () {
|
||||
|
||||
it("Will ignore boolean parameter", () => {
|
||||
const props = pass.props["barcodes"] || [];
|
||||
const oldAmountOfBarcodes = props && props.length || 0;
|
||||
const oldAmountOfBarcodes = (props && props.length) || 0;
|
||||
|
||||
// @ts-expect-error
|
||||
pass.barcode(true);
|
||||
@@ -192,7 +218,7 @@ describe("Passkit-generator", function () {
|
||||
|
||||
it("Will ignore numeric parameter", () => {
|
||||
const props = pass.props["barcodes"] || [];
|
||||
const oldAmountOfBarcodes = props && props.length || 0;
|
||||
const oldAmountOfBarcodes = (props && props.length) || 0;
|
||||
|
||||
// @ts-expect-error
|
||||
pass.barcodes(42);
|
||||
@@ -208,7 +234,7 @@ describe("Passkit-generator", function () {
|
||||
pass.barcodes({
|
||||
message: "28363516282",
|
||||
format: "PKBarcodeFormatPDF417",
|
||||
messageEncoding: "utf8"
|
||||
messageEncoding: "utf8",
|
||||
});
|
||||
|
||||
expect(pass.props["barcodes"].length).toBe(1);
|
||||
@@ -220,12 +246,14 @@ describe("Passkit-generator", function () {
|
||||
format: "PKBarcodeFormatPDF417",
|
||||
});
|
||||
|
||||
expect(pass.props["barcodes"][0].messageEncoding).toBe("iso-8859-1");
|
||||
expect(pass.props["barcodes"][0].messageEncoding).toBe(
|
||||
"iso-8859-1",
|
||||
);
|
||||
});
|
||||
|
||||
it("Will ignore objects without message property", () => {
|
||||
const props = pass.props["barcodes"] || [];
|
||||
const oldAmountOfBarcodes = props && props.length || 0;
|
||||
const oldAmountOfBarcodes = (props && props.length) || 0;
|
||||
|
||||
// @ts-expect-error
|
||||
pass.barcodes({
|
||||
@@ -237,12 +265,19 @@ describe("Passkit-generator", function () {
|
||||
|
||||
it("Will ignore non-Barcodes schema compliant objects", () => {
|
||||
// @ts-expect-error
|
||||
pass.barcodes(5, 10, 15, {
|
||||
pass.barcodes(
|
||||
5,
|
||||
10,
|
||||
15,
|
||||
{
|
||||
message: "28363516282",
|
||||
format: "PKBarcodeFormatPDF417"
|
||||
}, 7, 1);
|
||||
format: "PKBarcodeFormatPDF417",
|
||||
},
|
||||
7,
|
||||
1,
|
||||
);
|
||||
|
||||
expect(pass.props["barcodes"].length).toBe(1)
|
||||
expect(pass.props["barcodes"].length).toBe(1);
|
||||
});
|
||||
|
||||
it("Will reset barcodes content if parameter is null", () => {
|
||||
@@ -255,18 +290,18 @@ describe("Passkit-generator", function () {
|
||||
it("Will ignore non string or null arguments", function () {
|
||||
const oldBarcode = pass.props["barcode"] || undefined;
|
||||
|
||||
pass
|
||||
.barcodes("Message-22645272183")
|
||||
pass.barcodes("Message-22645272183")
|
||||
// @ts-expect-error
|
||||
.barcode(55)
|
||||
.barcode(55);
|
||||
|
||||
// unchanged
|
||||
expect(pass.props["barcode"]).toEqual(oldBarcode);
|
||||
});
|
||||
|
||||
it("Will reset backward value on null", () => {
|
||||
pass.barcodes("Message-22645272183")
|
||||
.barcode("PKBarcodeFormatAztec");
|
||||
pass.barcodes("Message-22645272183").barcode(
|
||||
"PKBarcodeFormatAztec",
|
||||
);
|
||||
|
||||
expect(pass.props["barcode"].format).toBe("PKBarcodeFormatAztec");
|
||||
|
||||
@@ -277,8 +312,7 @@ describe("Passkit-generator", function () {
|
||||
it("Won't apply changes if unknown format is passed", () => {
|
||||
const oldBarcode = pass.props["barcode"] || undefined;
|
||||
|
||||
pass
|
||||
.barcodes("Message-22645272183")
|
||||
pass.barcodes("Message-22645272183")
|
||||
// @ts-expect-error
|
||||
.barcode("PKBingoBongoFormat");
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
{
|
||||
"spec_dir": "spec",
|
||||
"spec_files": [
|
||||
"**/*.js"
|
||||
],
|
||||
"spec_files": ["**/*.js"],
|
||||
"stopSpecOnExpectationFailure": false,
|
||||
"random": true
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ describe("splitBufferBundle", () => {
|
||||
"de.lproj/background@2x.png": zeroBuffer,
|
||||
"it.lproj/thumbnail@2x.png": zeroBuffer,
|
||||
"thumbnail@2x.png": zeroBuffer,
|
||||
"background.png": zeroBuffer
|
||||
"background.png": zeroBuffer,
|
||||
};
|
||||
|
||||
const result = splitBufferBundle(payload);
|
||||
@@ -25,11 +25,11 @@ describe("splitBufferBundle", () => {
|
||||
},
|
||||
"it.lproj": {
|
||||
"thumbnail@2x.png": zeroBuffer,
|
||||
}
|
||||
},
|
||||
});
|
||||
expect(result[1]).toEqual({
|
||||
"thumbnail@2x.png": zeroBuffer,
|
||||
"background.png": zeroBuffer
|
||||
"background.png": zeroBuffer,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import { Certificates, FinalCertificates, PartitionedBundle, OverridesSupportedOptions, FactoryOptions } from "./schema";
|
||||
import {
|
||||
Certificates,
|
||||
FinalCertificates,
|
||||
PartitionedBundle,
|
||||
OverridesSupportedOptions,
|
||||
FactoryOptions,
|
||||
} from "./schema";
|
||||
import { getModelContents, readCertificatesFromOptions } from "./parser";
|
||||
import formatMessage from "./messages";
|
||||
|
||||
@@ -6,7 +12,8 @@ const abmCertificates = Symbol("certificates");
|
||||
const abmModel = Symbol("model");
|
||||
const abmOverrides = Symbol("overrides");
|
||||
|
||||
export interface AbstractFactoryOptions extends Omit<FactoryOptions, "certificates"> {
|
||||
export interface AbstractFactoryOptions
|
||||
extends Omit<FactoryOptions, "certificates"> {
|
||||
certificates?: Certificates;
|
||||
}
|
||||
|
||||
@@ -30,13 +37,13 @@ export async function createAbstractModel(options: AbstractFactoryOptions) {
|
||||
try {
|
||||
const [bundle, certificates] = await Promise.all([
|
||||
getModelContents(options.model),
|
||||
readCertificatesFromOptions(options.certificates)
|
||||
readCertificatesFromOptions(options.certificates),
|
||||
]);
|
||||
|
||||
return new AbstractModel({
|
||||
bundle,
|
||||
certificates,
|
||||
overrides: options.overrides
|
||||
overrides: options.overrides,
|
||||
});
|
||||
} catch (err) {
|
||||
throw new Error(formatMessage("CP_INIT_ERROR", "abstract model", err));
|
||||
@@ -51,7 +58,7 @@ export class AbstractModel {
|
||||
constructor(options: AbstractModelOptions) {
|
||||
this[abmModel] = options.bundle;
|
||||
this[abmCertificates] = options.certificates;
|
||||
this[abmOverrides] = options.overrides
|
||||
this[abmOverrides] = options.overrides;
|
||||
}
|
||||
|
||||
get certificates(): FinalCertificates {
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { Pass } from "./pass";
|
||||
import { FactoryOptions, BundleUnit, FinalCertificates, PartitionedBundle, OverridesSupportedOptions } from "./schema";
|
||||
import {
|
||||
FactoryOptions,
|
||||
BundleUnit,
|
||||
FinalCertificates,
|
||||
PartitionedBundle,
|
||||
OverridesSupportedOptions,
|
||||
} from "./schema";
|
||||
import formatMessage from "./messages";
|
||||
import { getModelContents, readCertificatesFromOptions } from "./parser";
|
||||
import { splitBufferBundle } from "./utils";
|
||||
@@ -16,9 +22,14 @@ import { AbstractModel, AbstractFactoryOptions } from "./abstract";
|
||||
export async function createPass(
|
||||
options: FactoryOptions | InstanceType<typeof AbstractModel>,
|
||||
additionalBuffers?: BundleUnit,
|
||||
abstractMissingData?: Omit<AbstractFactoryOptions, "model">
|
||||
abstractMissingData?: Omit<AbstractFactoryOptions, "model">,
|
||||
): Promise<Pass> {
|
||||
if (!(options && (options instanceof AbstractModel || Object.keys(options).length))) {
|
||||
if (
|
||||
!(
|
||||
options &&
|
||||
(options instanceof AbstractModel || Object.keys(options).length)
|
||||
)
|
||||
) {
|
||||
throw new Error(formatMessage("CP_NO_OPTS"));
|
||||
}
|
||||
|
||||
@@ -27,35 +38,62 @@ export async function createPass(
|
||||
let certificates: FinalCertificates;
|
||||
let overrides: OverridesSupportedOptions = {
|
||||
...(options.overrides || {}),
|
||||
...(abstractMissingData && abstractMissingData.overrides || {})
|
||||
...((abstractMissingData && abstractMissingData.overrides) ||
|
||||
{}),
|
||||
};
|
||||
|
||||
if (!(options.certificates && options.certificates.signerCert && options.certificates.signerKey) && abstractMissingData.certificates) {
|
||||
if (
|
||||
!(
|
||||
options.certificates &&
|
||||
options.certificates.signerCert &&
|
||||
options.certificates.signerKey
|
||||
) &&
|
||||
abstractMissingData.certificates
|
||||
) {
|
||||
certificates = Object.assign(
|
||||
options.certificates,
|
||||
await readCertificatesFromOptions(abstractMissingData.certificates)
|
||||
await readCertificatesFromOptions(
|
||||
abstractMissingData.certificates,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
certificates = options.certificates;
|
||||
}
|
||||
|
||||
return createPassInstance(options.bundle, certificates, overrides, additionalBuffers);
|
||||
return createPassInstance(
|
||||
options.bundle,
|
||||
certificates,
|
||||
overrides,
|
||||
additionalBuffers,
|
||||
);
|
||||
} else {
|
||||
const [bundle, certificates] = await Promise.all([
|
||||
getModelContents(options.model),
|
||||
readCertificatesFromOptions(options.certificates)
|
||||
readCertificatesFromOptions(options.certificates),
|
||||
]);
|
||||
|
||||
return createPassInstance(bundle, certificates, options.overrides, additionalBuffers);
|
||||
return createPassInstance(
|
||||
bundle,
|
||||
certificates,
|
||||
options.overrides,
|
||||
additionalBuffers,
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
throw new Error(formatMessage("CP_INIT_ERROR", "pass", err));
|
||||
}
|
||||
}
|
||||
|
||||
function createPassInstance(model: PartitionedBundle, certificates: FinalCertificates, overrides: OverridesSupportedOptions, additionalBuffers?: BundleUnit) {
|
||||
function createPassInstance(
|
||||
model: PartitionedBundle,
|
||||
certificates: FinalCertificates,
|
||||
overrides: OverridesSupportedOptions,
|
||||
additionalBuffers?: BundleUnit,
|
||||
) {
|
||||
if (additionalBuffers) {
|
||||
const [additionalL10n, additionalBundle] = splitBufferBundle(additionalBuffers);
|
||||
const [additionalL10n, additionalBundle] = splitBufferBundle(
|
||||
additionalBuffers,
|
||||
);
|
||||
Object.assign(model["l10nBundle"], additionalL10n);
|
||||
Object.assign(model["bundle"], additionalBundle);
|
||||
}
|
||||
@@ -63,6 +101,6 @@ function createPassInstance(model: PartitionedBundle, certificates: FinalCertifi
|
||||
return new Pass({
|
||||
model,
|
||||
certificates,
|
||||
overrides
|
||||
overrides,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -24,20 +24,28 @@ export default class FieldsArray extends Array {
|
||||
*/
|
||||
|
||||
push(...fieldsData: schema.Field[]): number {
|
||||
const validFields = fieldsData.reduce((acc: schema.Field[], current: schema.Field) => {
|
||||
if (!(typeof current === "object") || !schema.isValid(current, "field")) {
|
||||
const validFields = fieldsData.reduce(
|
||||
(acc: schema.Field[], current: schema.Field) => {
|
||||
if (
|
||||
!(typeof current === "object") ||
|
||||
!schema.isValid(current, "field")
|
||||
) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
if (this[poolSymbol].has(current.key)) {
|
||||
fieldsDebug(`Field with key "${current.key}" discarded: fields must be unique in pass scope.`);
|
||||
fieldsDebug(
|
||||
`Field with key "${current.key}" discarded: fields must be unique in pass scope.`,
|
||||
);
|
||||
} else {
|
||||
this[poolSymbol].add(current.key);
|
||||
acc.push(current);
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return Array.prototype.push.call(this, ...validFields);
|
||||
}
|
||||
@@ -58,9 +66,13 @@ export default class FieldsArray extends Array {
|
||||
* also uniqueKeys set
|
||||
*/
|
||||
|
||||
splice(start: number, deleteCount: number, ...items: schema.Field[]): schema.Field[] {
|
||||
splice(
|
||||
start: number,
|
||||
deleteCount: number,
|
||||
...items: schema.Field[]
|
||||
): schema.Field[] {
|
||||
const removeList = this.slice(start, deleteCount + start);
|
||||
removeList.forEach(item => this[poolSymbol].delete(item.key));
|
||||
removeList.forEach((item) => this[poolSymbol].delete(item.key));
|
||||
|
||||
return Array.prototype.splice.call(this, start, deleteCount, items);
|
||||
}
|
||||
|
||||
@@ -3,5 +3,5 @@ import { AbstractModel as AbstractModelClass } from "./abstract";
|
||||
|
||||
export { createPass } from "./factory";
|
||||
export { createAbstractModel } from "./abstract";
|
||||
export type Pass = InstanceType<typeof PassClass>
|
||||
export type AbstractModel = InstanceType<typeof AbstractModelClass>
|
||||
export type Pass = InstanceType<typeof PassClass>;
|
||||
export type AbstractModel = InstanceType<typeof AbstractModelClass>;
|
||||
|
||||
@@ -1,31 +1,51 @@
|
||||
const errors = {
|
||||
CP_INIT_ERROR: "Something went really bad in the %s initialization! Look at the log below this message. It should contain all the infos about the problem: \n%s",
|
||||
CP_NO_OPTS: "Cannot initialize the pass or abstract model creation: no options were passed.",
|
||||
CP_NO_CERTS: "Cannot initialize the pass creation: no valid certificates were passed.",
|
||||
PASSFILE_VALIDATION_FAILED: "Validation of pass type failed. Pass file is not a valid buffer or (more probably) does not respect the schema.\nRefer to https://apple.co/2Nvshvn to build a correct pass.",
|
||||
REQUIR_VALID_FAILED: "The options passed to Pass constructor does not meet the requirements.\nRefer to the documentation to compile them correctly.",
|
||||
MODEL_UNINITIALIZED: "Provided model ( %s ) matched but unitialized or may not contain icon or a valid pass.json.\nRefer to https://apple.co/2IhJr0Q, https://apple.co/2Nvshvn and documentation to fill the model correctly.",
|
||||
MODEL_NOT_VALID: "A model must be provided in form of path (string) or object { 'fileName': Buffer } in order to continue.",
|
||||
CP_INIT_ERROR:
|
||||
"Something went really bad in the %s initialization! Look at the log below this message. It should contain all the infos about the problem: \n%s",
|
||||
CP_NO_OPTS:
|
||||
"Cannot initialize the pass or abstract model creation: no options were passed.",
|
||||
CP_NO_CERTS:
|
||||
"Cannot initialize the pass creation: no valid certificates were passed.",
|
||||
PASSFILE_VALIDATION_FAILED:
|
||||
"Validation of pass type failed. Pass file is not a valid buffer or (more probably) does not respect the schema.\nRefer to https://apple.co/2Nvshvn to build a correct pass.",
|
||||
REQUIR_VALID_FAILED:
|
||||
"The options passed to Pass constructor does not meet the requirements.\nRefer to the documentation to compile them correctly.",
|
||||
MODEL_UNINITIALIZED:
|
||||
"Provided model ( %s ) matched but unitialized or may not contain icon or a valid pass.json.\nRefer to https://apple.co/2IhJr0Q, https://apple.co/2Nvshvn and documentation to fill the model correctly.",
|
||||
MODEL_NOT_VALID:
|
||||
"A model must be provided in form of path (string) or object { 'fileName': Buffer } in order to continue.",
|
||||
MODELF_NOT_FOUND: "Model %s not found. Provide a valid one to continue.",
|
||||
MODELF_FILE_NOT_FOUND: "File %s not found.",
|
||||
INVALID_CERTS: "Invalid certificate(s) loaded: %s. Please provide valid WWDR certificates and developer signer certificate and key (with passphrase).\nRefer to docs to obtain them.",
|
||||
INVALID_CERTS:
|
||||
"Invalid certificate(s) loaded: %s. Please provide valid WWDR certificates and developer signer certificate and key (with passphrase).\nRefer to docs to obtain them.",
|
||||
INVALID_CERT_PATH: "Invalid certificate loaded. %s does not exist.",
|
||||
TRSTYPE_REQUIRED: "Cannot proceed with pass creation. transitType field is required for boardingPasses.",
|
||||
OVV_KEYS_BADFORMAT: "Cannot proceed with pass creation due to bad keys format in overrides.",
|
||||
NO_PASS_TYPE: "Cannot proceed with pass creation. Model definition (pass.json) has no valid type in it.\nRefer to https://apple.co/2wzyL5J to choose a valid pass type."
|
||||
TRSTYPE_REQUIRED:
|
||||
"Cannot proceed with pass creation. transitType field is required for boardingPasses.",
|
||||
OVV_KEYS_BADFORMAT:
|
||||
"Cannot proceed with pass creation due to bad keys format in overrides.",
|
||||
NO_PASS_TYPE:
|
||||
"Cannot proceed with pass creation. Model definition (pass.json) has no valid type in it.\nRefer to https://apple.co/2wzyL5J to choose a valid pass type.",
|
||||
};
|
||||
|
||||
const debugMessages = {
|
||||
TRSTYPE_NOT_VALID: "Transit type changing rejected as not compliant with Apple Specifications. Transit type would become \"%s\" but should be in [PKTransitTypeAir, PKTransitTypeBoat, PKTransitTypeBus, PKTransitTypeGeneric, PKTransitTypeTrain]",
|
||||
BRC_NOT_SUPPORTED: "Format not found among barcodes. Cannot set backward compatibility.",
|
||||
BRC_FORMATTYPE_UNMATCH: "Format must be a string or null. Cannot set backward compatibility.",
|
||||
BRC_AUTC_MISSING_DATA: "Unable to autogenerate barcodes. Data is not a string.",
|
||||
BRC_BW_FORMAT_UNSUPPORTED: "This format is not supported (by Apple) for backward support. Please choose another one.",
|
||||
BRC_NO_POOL: "Cannot set barcode: no barcodes found. Please set barcodes first. Barcode is for retrocompatibility only.",
|
||||
TRSTYPE_NOT_VALID:
|
||||
'Transit type changing rejected as not compliant with Apple Specifications. Transit type would become "%s" but should be in [PKTransitTypeAir, PKTransitTypeBoat, PKTransitTypeBus, PKTransitTypeGeneric, PKTransitTypeTrain]',
|
||||
BRC_NOT_SUPPORTED:
|
||||
"Format not found among barcodes. Cannot set backward compatibility.",
|
||||
BRC_FORMATTYPE_UNMATCH:
|
||||
"Format must be a string or null. Cannot set backward compatibility.",
|
||||
BRC_AUTC_MISSING_DATA:
|
||||
"Unable to autogenerate barcodes. Data is not a string.",
|
||||
BRC_BW_FORMAT_UNSUPPORTED:
|
||||
"This format is not supported (by Apple) for backward support. Please choose another one.",
|
||||
BRC_NO_POOL:
|
||||
"Cannot set barcode: no barcodes found. Please set barcodes first. Barcode is for retrocompatibility only.",
|
||||
DATE_FORMAT_UNMATCH: "%s was not set due to incorrect date format.",
|
||||
NFC_INVALID: "Unable to set NFC properties: data not compliant with schema.",
|
||||
PRS_INVALID: "Unable to parse Personalization.json. File is not a valid JSON. Error: %s",
|
||||
PRS_REMOVED: "Personalization has been removed as it requires an NFC-enabled pass to work."
|
||||
NFC_INVALID:
|
||||
"Unable to set NFC properties: data not compliant with schema.",
|
||||
PRS_INVALID:
|
||||
"Unable to parse Personalization.json. File is not a valid JSON. Error: %s",
|
||||
PRS_REMOVED:
|
||||
"Personalization has been removed as it requires an NFC-enabled pass to work.",
|
||||
};
|
||||
|
||||
type AllMessages = keyof (typeof debugMessages & typeof errors);
|
||||
|
||||
179
src/parser.ts
179
src/parser.ts
@@ -1,8 +1,21 @@
|
||||
import * as path from "path";
|
||||
import forge from "node-forge";
|
||||
import formatMessage from "./messages";
|
||||
import { FactoryOptions, PartitionedBundle, BundleUnit, Certificates, FinalCertificates, isValid } from "./schema";
|
||||
import { removeHidden, splitBufferBundle, getAllFilesWithName, hasFilesWithName, deletePersonalization } from "./utils";
|
||||
import {
|
||||
FactoryOptions,
|
||||
PartitionedBundle,
|
||||
BundleUnit,
|
||||
Certificates,
|
||||
FinalCertificates,
|
||||
isValid,
|
||||
} from "./schema";
|
||||
import {
|
||||
removeHidden,
|
||||
splitBufferBundle,
|
||||
getAllFilesWithName,
|
||||
hasFilesWithName,
|
||||
deletePersonalization,
|
||||
} from "./utils";
|
||||
import fs from "fs";
|
||||
import debug from "debug";
|
||||
|
||||
@@ -27,10 +40,9 @@ export async function getModelContents(model: FactoryOptions["model"]) {
|
||||
}
|
||||
|
||||
const modelFiles = Object.keys(modelContents.bundle);
|
||||
const isModelInitialized = (
|
||||
const isModelInitialized =
|
||||
modelFiles.includes("pass.json") &&
|
||||
hasFilesWithName("icon", modelFiles, "startsWith")
|
||||
);
|
||||
hasFilesWithName("icon", modelFiles, "startsWith");
|
||||
|
||||
if (!isModelInitialized) {
|
||||
throw new Error(formatMessage("MODEL_UNINITIALIZED", "parse result"));
|
||||
@@ -46,19 +58,34 @@ export async function getModelContents(model: FactoryOptions["model"]) {
|
||||
return modelContents;
|
||||
}
|
||||
|
||||
const logoFullNames = getAllFilesWithName("personalizationLogo", modelFiles, "startsWith");
|
||||
if (!(logoFullNames.length && modelContents.bundle[personalizationJsonFile].length)) {
|
||||
const logoFullNames = getAllFilesWithName(
|
||||
"personalizationLogo",
|
||||
modelFiles,
|
||||
"startsWith",
|
||||
);
|
||||
if (
|
||||
!(
|
||||
logoFullNames.length &&
|
||||
modelContents.bundle[personalizationJsonFile].length
|
||||
)
|
||||
) {
|
||||
deletePersonalization(modelContents.bundle, logoFullNames);
|
||||
return modelContents;
|
||||
}
|
||||
|
||||
try {
|
||||
const parsedPersonalization = JSON.parse(modelContents.bundle[personalizationJsonFile].toString("utf8"));
|
||||
const isPersonalizationValid = isValid(parsedPersonalization, "personalizationDict");
|
||||
const parsedPersonalization = JSON.parse(
|
||||
modelContents.bundle[personalizationJsonFile].toString("utf8"),
|
||||
);
|
||||
const isPersonalizationValid = isValid(
|
||||
parsedPersonalization,
|
||||
"personalizationDict",
|
||||
);
|
||||
|
||||
if (!isPersonalizationValid) {
|
||||
[...logoFullNames, personalizationJsonFile]
|
||||
.forEach(file => delete modelContents.bundle[file]);
|
||||
[...logoFullNames, personalizationJsonFile].forEach(
|
||||
(file) => delete modelContents.bundle[file],
|
||||
);
|
||||
|
||||
return modelContents;
|
||||
}
|
||||
@@ -76,55 +103,70 @@ export async function getModelContents(model: FactoryOptions["model"]) {
|
||||
* @param model
|
||||
*/
|
||||
|
||||
export async function getModelFolderContents(model: string): Promise<PartitionedBundle> {
|
||||
export async function getModelFolderContents(
|
||||
model: string,
|
||||
): Promise<PartitionedBundle> {
|
||||
try {
|
||||
const modelPath = `${model}${!path.extname(model) && ".pass" || ""}`;
|
||||
const modelPath = `${model}${(!path.extname(model) && ".pass") || ""}`;
|
||||
const modelFilesList = await readDir(modelPath);
|
||||
|
||||
// No dot-starting files, manifest and signature
|
||||
const filteredFiles = removeHidden(modelFilesList)
|
||||
.filter(f => !/(manifest|signature)/i.test(f) && /.+$/.test(path.parse(f).ext));
|
||||
|
||||
const isModelInitialized = (
|
||||
filteredFiles.length &&
|
||||
hasFilesWithName("icon", filteredFiles, "startsWith")
|
||||
const filteredFiles = removeHidden(modelFilesList).filter(
|
||||
(f) =>
|
||||
!/(manifest|signature)/i.test(f) &&
|
||||
/.+$/.test(path.parse(f).ext),
|
||||
);
|
||||
|
||||
const isModelInitialized =
|
||||
filteredFiles.length &&
|
||||
hasFilesWithName("icon", filteredFiles, "startsWith");
|
||||
|
||||
// Icon is required to proceed
|
||||
if (!isModelInitialized) {
|
||||
throw new Error(formatMessage(
|
||||
"MODEL_UNINITIALIZED",
|
||||
path.parse(model).name
|
||||
));
|
||||
throw new Error(
|
||||
formatMessage("MODEL_UNINITIALIZED", path.parse(model).name),
|
||||
);
|
||||
}
|
||||
|
||||
// Splitting files from localization folders
|
||||
const rawBundleFiles = filteredFiles.filter(entry => !entry.includes(".lproj"));
|
||||
const l10nFolders = filteredFiles.filter(entry => entry.includes(".lproj"));
|
||||
|
||||
const rawBundleBuffers = await Promise.all(
|
||||
rawBundleFiles.map(file => readFile(path.resolve(modelPath, file)))
|
||||
const rawBundleFiles = filteredFiles.filter(
|
||||
(entry) => !entry.includes(".lproj"),
|
||||
);
|
||||
const l10nFolders = filteredFiles.filter((entry) =>
|
||||
entry.includes(".lproj"),
|
||||
);
|
||||
|
||||
const bundle: BundleUnit = Object.assign({},
|
||||
...rawBundleFiles.map((fileName, index) => ({ [fileName]: rawBundleBuffers[index] }))
|
||||
const rawBundleBuffers = await Promise.all(
|
||||
rawBundleFiles.map((file) =>
|
||||
readFile(path.resolve(modelPath, file)),
|
||||
),
|
||||
);
|
||||
|
||||
const bundle: BundleUnit = Object.assign(
|
||||
{},
|
||||
...rawBundleFiles.map((fileName, index) => ({
|
||||
[fileName]: rawBundleBuffers[index],
|
||||
})),
|
||||
);
|
||||
|
||||
// Reading concurrently localizations folder
|
||||
// and their files and their buffers
|
||||
const L10N_FilesListByFolder: Array<BundleUnit> = await Promise.all(
|
||||
l10nFolders.map(async folderPath => {
|
||||
l10nFolders.map(async (folderPath) => {
|
||||
// Reading current folder
|
||||
const currentLangPath = path.join(modelPath, folderPath);
|
||||
|
||||
const files = await readDir(currentLangPath);
|
||||
// Transforming files path to a model-relative path
|
||||
const validFiles = removeHidden(files)
|
||||
.map(file => path.join(currentLangPath, file));
|
||||
const validFiles = removeHidden(files).map((file) =>
|
||||
path.join(currentLangPath, file),
|
||||
);
|
||||
|
||||
// Getting all the buffers from file paths
|
||||
const buffers = await Promise.all(
|
||||
validFiles.map(file => readFile(file).catch(() => Buffer.alloc(0)))
|
||||
validFiles.map((file) =>
|
||||
readFile(file).catch(() => Buffer.alloc(0)),
|
||||
),
|
||||
);
|
||||
|
||||
// Assigning each file path to its buffer
|
||||
@@ -140,34 +182,37 @@ export async function getModelFolderContents(model: string): Promise<Partitioned
|
||||
|
||||
return {
|
||||
...acc,
|
||||
[fileName]: buffers[index]
|
||||
[fileName]: buffers[index],
|
||||
};
|
||||
}, {});
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
const l10nBundle: PartitionedBundle["l10nBundle"] = Object.assign(
|
||||
{},
|
||||
...L10N_FilesListByFolder
|
||||
.map((folder, index) => ({ [l10nFolders[index]]: folder }))
|
||||
...L10N_FilesListByFolder.map((folder, index) => ({
|
||||
[l10nFolders[index]]: folder,
|
||||
})),
|
||||
);
|
||||
|
||||
return {
|
||||
bundle,
|
||||
l10nBundle
|
||||
l10nBundle,
|
||||
};
|
||||
} catch (err) {
|
||||
if (err?.code === "ENOENT") {
|
||||
if (err.syscall === "open") {
|
||||
// file opening failed
|
||||
throw new Error(formatMessage("MODELF_NOT_FOUND", err.path))
|
||||
throw new Error(formatMessage("MODELF_NOT_FOUND", err.path));
|
||||
} else if (err.syscall === "scandir") {
|
||||
// directory reading failed
|
||||
const pathContents = (err.path as string).split(/(\/|\\\?)/);
|
||||
throw new Error(formatMessage(
|
||||
throw new Error(
|
||||
formatMessage(
|
||||
"MODELF_FILE_NOT_FOUND",
|
||||
pathContents[pathContents.length - 1]
|
||||
))
|
||||
pathContents[pathContents.length - 1],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,7 +227,8 @@ export async function getModelFolderContents(model: string): Promise<Partitioned
|
||||
*/
|
||||
|
||||
export function getModelBufferContents(model: BundleUnit): PartitionedBundle {
|
||||
const rawBundle = removeHidden(Object.keys(model)).reduce<BundleUnit>((acc, current) => {
|
||||
const rawBundle = removeHidden(Object.keys(model)).reduce<BundleUnit>(
|
||||
(acc, current) => {
|
||||
// Checking if current file is one of the autogenerated ones or if its
|
||||
// content is not available
|
||||
|
||||
@@ -191,18 +237,18 @@ export function getModelBufferContents(model: BundleUnit): PartitionedBundle {
|
||||
}
|
||||
|
||||
return { ...acc, [current]: model[current] };
|
||||
}, {});
|
||||
},
|
||||
{},
|
||||
);
|
||||
|
||||
const bundleKeys = Object.keys(rawBundle);
|
||||
|
||||
const isModelInitialized = (
|
||||
bundleKeys.length &&
|
||||
hasFilesWithName("icon", bundleKeys, "startsWith")
|
||||
);
|
||||
const isModelInitialized =
|
||||
bundleKeys.length && hasFilesWithName("icon", bundleKeys, "startsWith");
|
||||
|
||||
// Icon is required to proceed
|
||||
if (!isModelInitialized) {
|
||||
throw new Error(formatMessage("MODEL_UNINITIALIZED", "Buffers"))
|
||||
throw new Error(formatMessage("MODEL_UNINITIALIZED", "Buffers"));
|
||||
}
|
||||
|
||||
// separing localization folders from bundle files
|
||||
@@ -210,7 +256,7 @@ export function getModelBufferContents(model: BundleUnit): PartitionedBundle {
|
||||
|
||||
return {
|
||||
bundle,
|
||||
l10nBundle
|
||||
l10nBundle,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -224,8 +270,16 @@ type flatCertificates = Omit<Certificates, "signerKey"> & {
|
||||
signerKey: string;
|
||||
};
|
||||
|
||||
export async function readCertificatesFromOptions(options: Certificates): Promise<FinalCertificates> {
|
||||
if (!(options && Object.keys(options).length && isValid(options, "certificatesSchema"))) {
|
||||
export async function readCertificatesFromOptions(
|
||||
options: Certificates,
|
||||
): Promise<FinalCertificates> {
|
||||
if (
|
||||
!(
|
||||
options &&
|
||||
Object.keys(options).length &&
|
||||
isValid(options, "certificatesSchema")
|
||||
)
|
||||
) {
|
||||
throw new Error(formatMessage("CP_NO_CERTS"));
|
||||
}
|
||||
|
||||
@@ -239,11 +293,12 @@ export async function readCertificatesFromOptions(options: Certificates): Promis
|
||||
|
||||
// if the signerKey is an object, we want to get
|
||||
// all the real contents and don't care of passphrase
|
||||
const flattenedDocs = Object.assign({}, options, { signerKey }) as flatCertificates;
|
||||
const flattenedDocs = Object.assign({}, options, {
|
||||
signerKey,
|
||||
}) as flatCertificates;
|
||||
|
||||
// We read the contents
|
||||
const rawContentsPromises = Object.keys(flattenedDocs)
|
||||
.map(key => {
|
||||
const rawContentsPromises = Object.keys(flattenedDocs).map((key) => {
|
||||
const content = flattenedDocs[key];
|
||||
|
||||
if (!!path.parse(content).ext) {
|
||||
@@ -259,10 +314,10 @@ export async function readCertificatesFromOptions(options: Certificates): Promis
|
||||
const parsedContents = await Promise.all(rawContentsPromises);
|
||||
const pemParsedContents = parsedContents.map((file, index) => {
|
||||
const certName = Object.keys(options)[index];
|
||||
const passphrase = (
|
||||
typeof options.signerKey === "object" &&
|
||||
options.signerKey?.passphrase
|
||||
) || undefined;
|
||||
const passphrase =
|
||||
(typeof options.signerKey === "object" &&
|
||||
options.signerKey?.passphrase) ||
|
||||
undefined;
|
||||
|
||||
const pem = parsePEM(certName, file, passphrase);
|
||||
|
||||
@@ -279,7 +334,9 @@ export async function readCertificatesFromOptions(options: Certificates): Promis
|
||||
throw err;
|
||||
}
|
||||
|
||||
throw new Error(formatMessage("INVALID_CERT_PATH", path.parse(err.path).base));
|
||||
throw new Error(
|
||||
formatMessage("INVALID_CERT_PATH", path.parse(err.path).base),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
242
src/pass.ts
242
src/pass.ts
@@ -7,7 +7,13 @@ import { ZipFile } from "yazl";
|
||||
import * as schema from "./schema";
|
||||
import formatMessage from "./messages";
|
||||
import FieldsArray from "./fieldsArray";
|
||||
import { generateStringFile, dateToW3CString, isValidRGB, deletePersonalization, getAllFilesWithName } from "./utils";
|
||||
import {
|
||||
generateStringFile,
|
||||
dateToW3CString,
|
||||
isValidRGB,
|
||||
deletePersonalization,
|
||||
getAllFilesWithName,
|
||||
} from "./utils";
|
||||
|
||||
const barcodeDebug = debug("passkit:barcode");
|
||||
const genericDebug = debug("passkit:generic");
|
||||
@@ -20,7 +26,7 @@ const propsSchemaMap = new Map<string, schema.Schema>([
|
||||
["barcode", "barcode"],
|
||||
["beacons", "beaconsDict"],
|
||||
["locations", "locationsDict"],
|
||||
["nfc", "nfcDict"]
|
||||
["nfc", "nfcDict"],
|
||||
]);
|
||||
|
||||
export class Pass {
|
||||
@@ -42,7 +48,9 @@ export class Pass {
|
||||
|
||||
private Certificates: schema.FinalCertificates;
|
||||
private [transitType]: string = "";
|
||||
private l10nTranslations: { [languageCode: string]: { [placeholder: string]: string } } = {};
|
||||
private l10nTranslations: {
|
||||
[languageCode: string]: { [placeholder: string]: string };
|
||||
} = {};
|
||||
|
||||
constructor(options: schema.PassInstance) {
|
||||
if (!schema.isValid(options, "instance")) {
|
||||
@@ -54,28 +62,37 @@ export class Pass {
|
||||
this.bundle = { ...options.model.bundle };
|
||||
|
||||
try {
|
||||
this.passCore = JSON.parse(this.bundle["pass.json"].toString("utf8"));
|
||||
this.passCore = JSON.parse(
|
||||
this.bundle["pass.json"].toString("utf8"),
|
||||
);
|
||||
} catch (err) {
|
||||
throw new Error(formatMessage("PASSFILE_VALIDATION_FAILED"));
|
||||
}
|
||||
|
||||
// Parsing the options and extracting only the valid ones.
|
||||
const validOverrides = schema.getValidated(options.overrides || {}, "supportedOptions") as schema.OverridesSupportedOptions;
|
||||
const validOverrides = schema.getValidated(
|
||||
options.overrides || {},
|
||||
"supportedOptions",
|
||||
) as schema.OverridesSupportedOptions;
|
||||
|
||||
if (validOverrides === null) {
|
||||
throw new Error(formatMessage("OVV_KEYS_BADFORMAT"))
|
||||
throw new Error(formatMessage("OVV_KEYS_BADFORMAT"));
|
||||
}
|
||||
|
||||
this.type = Object.keys(this.passCore)
|
||||
.find(key => /(boardingPass|eventTicket|coupon|generic|storeCard)/.test(key)) as keyof schema.ValidPassType;
|
||||
this.type = Object.keys(this.passCore).find((key) =>
|
||||
/(boardingPass|eventTicket|coupon|generic|storeCard)/.test(key),
|
||||
) as keyof schema.ValidPassType;
|
||||
|
||||
if (!this.type) {
|
||||
throw new Error(formatMessage("NO_PASS_TYPE"));
|
||||
}
|
||||
|
||||
// Parsing and validating pass.json keys
|
||||
const passCoreKeys = Object.keys(this.passCore) as (keyof schema.ValidPass)[];
|
||||
const validatedPassKeys = passCoreKeys.reduce<schema.ValidPass>((acc, current) => {
|
||||
const passCoreKeys = Object.keys(
|
||||
this.passCore,
|
||||
) as (keyof schema.ValidPass)[];
|
||||
const validatedPassKeys = passCoreKeys.reduce<schema.ValidPass>(
|
||||
(acc, current) => {
|
||||
if (this.type === current) {
|
||||
// We want to exclude type keys (eventTicket,
|
||||
// boardingPass, ecc.) and their content
|
||||
@@ -94,37 +111,52 @@ export class Pass {
|
||||
if (Array.isArray(this.passCore[current])) {
|
||||
const valid = getValidInArray<schema.ArrayPassSchema>(
|
||||
currentSchema,
|
||||
this.passCore[current] as schema.ArrayPassSchema[]
|
||||
this.passCore[current] as schema.ArrayPassSchema[],
|
||||
);
|
||||
return { ...acc, [current]: valid };
|
||||
} else {
|
||||
return {
|
||||
...acc,
|
||||
[current]: schema.isValid(
|
||||
[current]:
|
||||
(schema.isValid(
|
||||
this.passCore[current],
|
||||
currentSchema
|
||||
) && this.passCore[current] || undefined
|
||||
currentSchema,
|
||||
) &&
|
||||
this.passCore[current]) ||
|
||||
undefined,
|
||||
};
|
||||
}
|
||||
}, {});
|
||||
},
|
||||
{},
|
||||
);
|
||||
|
||||
this[passProps] = {
|
||||
...(validatedPassKeys || {}),
|
||||
...(validOverrides || {})
|
||||
...(validOverrides || {}),
|
||||
};
|
||||
|
||||
if (this.type === "boardingPass" && this.passCore[this.type]["transitType"]) {
|
||||
if (
|
||||
this.type === "boardingPass" &&
|
||||
this.passCore[this.type]["transitType"]
|
||||
) {
|
||||
// We might want to generate a boarding pass without setting manually
|
||||
// in the code the transit type but right in the model;
|
||||
this[transitType] = this.passCore[this.type]["transitType"];
|
||||
}
|
||||
|
||||
this._fields = ["primaryFields", "secondaryFields", "auxiliaryFields", "backFields", "headerFields"];
|
||||
this._fields.forEach(fieldName => {
|
||||
this._fields = [
|
||||
"primaryFields",
|
||||
"secondaryFields",
|
||||
"auxiliaryFields",
|
||||
"backFields",
|
||||
"headerFields",
|
||||
];
|
||||
this._fields.forEach((fieldName) => {
|
||||
this[fieldName] = new FieldsArray(
|
||||
this.fieldsKeys,
|
||||
...(this.passCore[this.type][fieldName] || [])
|
||||
.filter(field => schema.isValid(field, "field"))
|
||||
...(this.passCore[this.type][fieldName] || []).filter((field) =>
|
||||
schema.isValid(field, "field"),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -146,13 +178,19 @@ export class Pass {
|
||||
*/
|
||||
const currentBundleFiles = Object.keys(this.bundle);
|
||||
|
||||
if (!this[passProps].nfc && currentBundleFiles.includes("personalization.json")) {
|
||||
if (
|
||||
!this[passProps].nfc &&
|
||||
currentBundleFiles.includes("personalization.json")
|
||||
) {
|
||||
genericDebug(formatMessage("PRS_REMOVED"));
|
||||
deletePersonalization(this.bundle, getAllFilesWithName(
|
||||
deletePersonalization(
|
||||
this.bundle,
|
||||
getAllFilesWithName(
|
||||
"personalizationLogo",
|
||||
currentBundleFiles,
|
||||
"startsWith"
|
||||
));
|
||||
"startsWith",
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const finalBundle = { ...this.bundle } as schema.BundleUnit;
|
||||
@@ -161,7 +199,7 @@ export class Pass {
|
||||
* Iterating through languages and generating pass.string file
|
||||
*/
|
||||
|
||||
Object.keys(this.l10nTranslations).forEach(lang => {
|
||||
Object.keys(this.l10nTranslations).forEach((lang) => {
|
||||
const strings = generateStringFile(this.l10nTranslations[lang]);
|
||||
const langInBundles = `${lang}.lproj`;
|
||||
|
||||
@@ -176,13 +214,21 @@ export class Pass {
|
||||
this.l10nBundles[langInBundles] = {};
|
||||
}
|
||||
|
||||
this.l10nBundles[langInBundles]["pass.strings"] = Buffer.concat([
|
||||
this.l10nBundles[langInBundles]["pass.strings"] || Buffer.alloc(0),
|
||||
strings
|
||||
this.l10nBundles[langInBundles][
|
||||
"pass.strings"
|
||||
] = Buffer.concat([
|
||||
this.l10nBundles[langInBundles]["pass.strings"] ||
|
||||
Buffer.alloc(0),
|
||||
strings,
|
||||
]);
|
||||
}
|
||||
|
||||
if (!(this.l10nBundles[langInBundles] && Object.keys(this.l10nBundles[langInBundles]).length)) {
|
||||
if (
|
||||
!(
|
||||
this.l10nBundles[langInBundles] &&
|
||||
Object.keys(this.l10nBundles[langInBundles]).length
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -194,11 +240,20 @@ export class Pass {
|
||||
* composition.
|
||||
*/
|
||||
|
||||
Object.assign(finalBundle, ...Object.keys(this.l10nBundles[langInBundles])
|
||||
.map(fileName => {
|
||||
const fullPath = path.join(langInBundles, fileName).replace(/\\/, "/");
|
||||
return { [fullPath]: this.l10nBundles[langInBundles][fileName] };
|
||||
})
|
||||
Object.assign(
|
||||
finalBundle,
|
||||
...Object.keys(this.l10nBundles[langInBundles]).map(
|
||||
(fileName) => {
|
||||
const fullPath = path
|
||||
.join(langInBundles, fileName)
|
||||
.replace(/\\/, "/");
|
||||
return {
|
||||
[fullPath]: this.l10nBundles[langInBundles][
|
||||
fileName
|
||||
],
|
||||
};
|
||||
},
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -207,7 +262,8 @@ export class Pass {
|
||||
* and returning the compiled manifest
|
||||
*/
|
||||
const archive = new ZipFile();
|
||||
const manifest = Object.keys(finalBundle).reduce<schema.Manifest>((acc, current) => {
|
||||
const manifest = Object.keys(finalBundle).reduce<schema.Manifest>(
|
||||
(acc, current) => {
|
||||
let hashFlow = forge.md.sha1.create();
|
||||
|
||||
hashFlow.update(finalBundle[current].toString("binary"));
|
||||
@@ -215,12 +271,17 @@ export class Pass {
|
||||
acc[current] = hashFlow.digest().toHex();
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
},
|
||||
{},
|
||||
);
|
||||
|
||||
const signatureBuffer = this._sign(manifest);
|
||||
|
||||
archive.addBuffer(signatureBuffer, "signature");
|
||||
archive.addBuffer(Buffer.from(JSON.stringify(manifest)), "manifest.json");
|
||||
archive.addBuffer(
|
||||
Buffer.from(JSON.stringify(manifest)),
|
||||
"manifest.json",
|
||||
);
|
||||
const passStream = new Stream.PassThrough();
|
||||
|
||||
archive.outputStream.pipe(passStream);
|
||||
@@ -242,8 +303,15 @@ export class Pass {
|
||||
* @see https://apple.co/2KOv0OW - Passes support localization
|
||||
*/
|
||||
|
||||
localize(lang: string, translations?: { [placeholder: string]: string }): this {
|
||||
if (lang && typeof lang === "string" && (typeof translations === "object" || translations === undefined)) {
|
||||
localize(
|
||||
lang: string,
|
||||
translations?: { [placeholder: string]: string },
|
||||
): this {
|
||||
if (
|
||||
lang &&
|
||||
typeof lang === "string" &&
|
||||
(typeof translations === "object" || translations === undefined)
|
||||
) {
|
||||
this.l10nTranslations[lang] = translations || {};
|
||||
}
|
||||
|
||||
@@ -292,7 +360,7 @@ export class Pass {
|
||||
*/
|
||||
|
||||
beacons(resetFlag: null): this;
|
||||
beacons(...data: schema.Beacon[]): this
|
||||
beacons(...data: schema.Beacon[]): this;
|
||||
beacons(...data: (schema.Beacon | null)[]): this {
|
||||
if (data[0] === null) {
|
||||
delete this[passProps]["beacons"];
|
||||
@@ -322,7 +390,10 @@ export class Pass {
|
||||
return this;
|
||||
}
|
||||
|
||||
const valid = processRelevancySet("locations", data as schema.Location[]);
|
||||
const valid = processRelevancySet(
|
||||
"locations",
|
||||
data as schema.Location[],
|
||||
);
|
||||
|
||||
if (valid.length) {
|
||||
this[passProps]["locations"] = valid;
|
||||
@@ -390,19 +461,28 @@ export class Pass {
|
||||
* Validation assign default value to missing parameters (if any).
|
||||
*/
|
||||
|
||||
const validBarcodes = data.reduce<schema.Barcode[]>((acc, current) => {
|
||||
const validBarcodes = data.reduce<schema.Barcode[]>(
|
||||
(acc, current) => {
|
||||
if (!(current && current instanceof Object)) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
const validated = schema.getValidated(current, "barcode");
|
||||
|
||||
if (!(validated && validated instanceof Object && Object.keys(validated).length)) {
|
||||
if (
|
||||
!(
|
||||
validated &&
|
||||
validated instanceof Object &&
|
||||
Object.keys(validated).length
|
||||
)
|
||||
) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
return [...acc, validated] as schema.Barcode[];
|
||||
}, []);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
if (validBarcodes.length) {
|
||||
this[passProps]["barcodes"] = validBarcodes;
|
||||
@@ -446,7 +526,9 @@ export class Pass {
|
||||
}
|
||||
|
||||
// Checking which object among barcodes has the same format of the specified one.
|
||||
const index = barcodes.findIndex(b => b.format.toLowerCase().includes(chosenFormat.toLowerCase()));
|
||||
const index = barcodes.findIndex((b) =>
|
||||
b.format.toLowerCase().includes(chosenFormat.toLowerCase()),
|
||||
);
|
||||
|
||||
if (index === -1) {
|
||||
barcodeDebug(formatMessage("BRC_NOT_SUPPORTED"));
|
||||
@@ -472,7 +554,14 @@ export class Pass {
|
||||
return this;
|
||||
}
|
||||
|
||||
if (!(data && typeof data === "object" && !Array.isArray(data) && schema.isValid(data, "nfcDict"))) {
|
||||
if (
|
||||
!(
|
||||
data &&
|
||||
typeof data === "object" &&
|
||||
!Array.isArray(data) &&
|
||||
schema.isValid(data, "nfcDict")
|
||||
)
|
||||
) {
|
||||
genericDebug(formatMessage("NFC_INVALID"));
|
||||
return this;
|
||||
}
|
||||
@@ -505,7 +594,10 @@ export class Pass {
|
||||
private _sign(manifest: schema.Manifest): Buffer {
|
||||
const signature = forge.pkcs7.createSignedData();
|
||||
|
||||
signature.content = forge.util.createBuffer(JSON.stringify(manifest), "utf8");
|
||||
signature.content = forge.util.createBuffer(
|
||||
JSON.stringify(manifest),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
signature.addCertificate(this.Certificates.wwdr);
|
||||
signature.addCertificate(this.Certificates.signerCert);
|
||||
@@ -523,14 +615,18 @@ export class Pass {
|
||||
key: this.Certificates.signerKey,
|
||||
certificate: this.Certificates.signerCert,
|
||||
digestAlgorithm: forge.pki.oids.sha1,
|
||||
authenticatedAttributes: [{
|
||||
authenticatedAttributes: [
|
||||
{
|
||||
type: forge.pki.oids.contentType,
|
||||
value: forge.pki.oids.data
|
||||
}, {
|
||||
value: forge.pki.oids.data,
|
||||
},
|
||||
{
|
||||
type: forge.pki.oids.messageDigest,
|
||||
}, {
|
||||
},
|
||||
{
|
||||
type: forge.pki.oids.signingTime,
|
||||
}]
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -554,7 +650,10 @@ export class Pass {
|
||||
* of beautiful things. ¯\_(ツ)_/¯
|
||||
*/
|
||||
|
||||
return Buffer.from(forge.asn1.toDer(signature.toAsn1()).getBytes(), "binary");
|
||||
return Buffer.from(
|
||||
forge.asn1.toDer(signature.toAsn1()).getBytes(),
|
||||
"binary",
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -566,7 +665,9 @@ export class Pass {
|
||||
*/
|
||||
|
||||
private _patch(passCoreBuffer: Buffer): Buffer {
|
||||
const passFile = JSON.parse(passCoreBuffer.toString()) as schema.ValidPass;
|
||||
const passFile = JSON.parse(
|
||||
passCoreBuffer.toString(),
|
||||
) as schema.ValidPass;
|
||||
|
||||
if (Object.keys(this[passProps]).length) {
|
||||
/*
|
||||
@@ -575,14 +676,22 @@ export class Pass {
|
||||
* and then delete it from the passFile.
|
||||
*/
|
||||
|
||||
const passColors = ["backgroundColor", "foregroundColor", "labelColor"] as Array<keyof schema.PassColors>;
|
||||
passColors.filter(v => this[passProps][v] && !isValidRGB(this[passProps][v]))
|
||||
.forEach(v => delete this[passProps][v]);
|
||||
const passColors = [
|
||||
"backgroundColor",
|
||||
"foregroundColor",
|
||||
"labelColor",
|
||||
] as Array<keyof schema.PassColors>;
|
||||
passColors
|
||||
.filter(
|
||||
(v) =>
|
||||
this[passProps][v] && !isValidRGB(this[passProps][v]),
|
||||
)
|
||||
.forEach((v) => delete this[passProps][v]);
|
||||
|
||||
Object.assign(passFile, this[passProps]);
|
||||
}
|
||||
|
||||
this._fields.forEach(field => {
|
||||
this._fields.forEach((field) => {
|
||||
passFile[this.type][field] = this[field];
|
||||
});
|
||||
|
||||
@@ -627,8 +736,14 @@ function barcodesFromUncompleteData(message: string): schema.Barcode[] {
|
||||
"PKBarcodeFormatQR",
|
||||
"PKBarcodeFormatPDF417",
|
||||
"PKBarcodeFormatAztec",
|
||||
"PKBarcodeFormatCode128"
|
||||
].map(format => schema.getValidated({ format, message }, "barcode") as schema.Barcode);
|
||||
"PKBarcodeFormatCode128",
|
||||
].map(
|
||||
(format) =>
|
||||
schema.getValidated(
|
||||
{ format, message },
|
||||
"barcode",
|
||||
) as schema.Barcode,
|
||||
);
|
||||
}
|
||||
|
||||
function processRelevancySet<T>(key: string, data: T[]): T[] {
|
||||
@@ -636,7 +751,10 @@ function processRelevancySet<T>(key: string, data: T[]): T[] {
|
||||
}
|
||||
|
||||
function getValidInArray<T>(schemaName: schema.Schema, contents: T[]): T[] {
|
||||
return contents.filter(current => Object.keys(current).length && schema.isValid(current, schemaName));
|
||||
return contents.filter(
|
||||
(current) =>
|
||||
Object.keys(current).length && schema.isValid(current, schemaName),
|
||||
);
|
||||
}
|
||||
|
||||
function processDate(key: string, date: Date): string | null {
|
||||
|
||||
200
src/schema.ts
200
src/schema.ts
@@ -10,10 +10,12 @@ export interface Manifest {
|
||||
export interface Certificates {
|
||||
wwdr?: string;
|
||||
signerCert?: string;
|
||||
signerKey?: {
|
||||
signerKey?:
|
||||
| {
|
||||
keyFile: string;
|
||||
passphrase?: string;
|
||||
} | string;
|
||||
}
|
||||
| string;
|
||||
}
|
||||
|
||||
export interface FactoryOptions {
|
||||
@@ -29,7 +31,7 @@ export interface BundleUnit {
|
||||
export interface PartitionedBundle {
|
||||
bundle: BundleUnit;
|
||||
l10nBundle: {
|
||||
[key: string]: BundleUnit
|
||||
[key: string]: BundleUnit;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -49,17 +51,24 @@ export interface PassInstance {
|
||||
// * JOI Schemas + Related Interfaces * //
|
||||
// ************************************ //
|
||||
|
||||
const certificatesSchema = Joi.object().keys({
|
||||
const certificatesSchema = Joi.object()
|
||||
.keys({
|
||||
wwdr: Joi.alternatives(Joi.binary(), Joi.string()).required(),
|
||||
signerCert: Joi.alternatives(Joi.binary(), Joi.string()).required(),
|
||||
signerKey: Joi.alternatives().try(
|
||||
signerKey: Joi.alternatives()
|
||||
.try(
|
||||
Joi.object().keys({
|
||||
keyFile: Joi.alternatives(Joi.binary(), Joi.string()).required(),
|
||||
keyFile: Joi.alternatives(
|
||||
Joi.binary(),
|
||||
Joi.string(),
|
||||
).required(),
|
||||
passphrase: Joi.string().required(),
|
||||
}),
|
||||
Joi.alternatives(Joi.binary(), Joi.string())
|
||||
).required()
|
||||
}).required();
|
||||
Joi.alternatives(Joi.binary(), Joi.string()),
|
||||
)
|
||||
.required(),
|
||||
})
|
||||
.required();
|
||||
|
||||
const instance = Joi.object().keys({
|
||||
model: Joi.alternatives(Joi.object(), Joi.string()).required(),
|
||||
@@ -88,7 +97,8 @@ export interface OverridesSupportedOptions {
|
||||
maxDistance?: number;
|
||||
}
|
||||
|
||||
const supportedOptions = Joi.object().keys({
|
||||
const supportedOptions = Joi.object()
|
||||
.keys({
|
||||
serialNumber: Joi.string(),
|
||||
description: Joi.string(),
|
||||
organizationName: Joi.string(),
|
||||
@@ -98,7 +108,9 @@ const supportedOptions = Joi.object().keys({
|
||||
associatedStoreIdentifiers: Joi.array().items(Joi.number()),
|
||||
userInfo: Joi.alternatives(Joi.object().unknown(), Joi.array()),
|
||||
// parsing url as set of words and nums followed by dots, optional port and any possible path after
|
||||
webServiceURL: Joi.string().regex(/https?:\/\/(?:[a-z0-9]+\.?)+(?::\d{2,})?(?:\/[\S]+)*/),
|
||||
webServiceURL: Joi.string().regex(
|
||||
/https?:\/\/(?:[a-z0-9]+\.?)+(?::\d{2,})?(?:\/[\S]+)*/,
|
||||
),
|
||||
authenticationToken: Joi.string().min(16),
|
||||
sharingProhibited: Joi.boolean(),
|
||||
backgroundColor: Joi.string().min(10).max(16),
|
||||
@@ -108,8 +120,8 @@ const supportedOptions = Joi.object().keys({
|
||||
suppressStripShine: Joi.boolean(),
|
||||
logoText: Joi.string(),
|
||||
maxDistance: Joi.number().positive(),
|
||||
}).with("webServiceURL", "authenticationToken");
|
||||
|
||||
})
|
||||
.with("webServiceURL", "authenticationToken");
|
||||
|
||||
/* For a correct usage of semantics, please refer to https://apple.co/2I66Phk */
|
||||
|
||||
@@ -130,7 +142,7 @@ interface PersonNameComponent {
|
||||
|
||||
const personNameComponents = Joi.object().keys({
|
||||
givenName: Joi.string().required(),
|
||||
familyName: Joi.string().required()
|
||||
familyName: Joi.string().required(),
|
||||
});
|
||||
|
||||
interface Seat {
|
||||
@@ -148,12 +160,12 @@ const seat = Joi.object().keys({
|
||||
seatNumber: Joi.string(),
|
||||
seatIdentifier: Joi.string(),
|
||||
seatType: Joi.string(),
|
||||
seatDescription: Joi.string()
|
||||
seatDescription: Joi.string(),
|
||||
});
|
||||
|
||||
const location = Joi.object().keys({
|
||||
latitude: Joi.number().required(),
|
||||
longitude: Joi.number().required()
|
||||
longitude: Joi.number().required(),
|
||||
});
|
||||
|
||||
interface Semantics {
|
||||
@@ -201,7 +213,15 @@ interface Semantics {
|
||||
venueEntrance?: string;
|
||||
venuePhoneNumber?: string;
|
||||
venueRoom?: string;
|
||||
eventType?: "PKEventTypeGeneric" | "PKEventTypeLivePerformance" | "PKEventTypeMovie" | "PKEventTypeSports" | "PKEventTypeConference" | "PKEventTypeConvention" | "PKEventTypeWorkshop" | "PKEventTypeSocialGathering";
|
||||
eventType?:
|
||||
| "PKEventTypeGeneric"
|
||||
| "PKEventTypeLivePerformance"
|
||||
| "PKEventTypeMovie"
|
||||
| "PKEventTypeSports"
|
||||
| "PKEventTypeConference"
|
||||
| "PKEventTypeConvention"
|
||||
| "PKEventTypeWorkshop"
|
||||
| "PKEventTypeSocialGathering";
|
||||
eventStartDate?: string;
|
||||
eventEndDate?: string;
|
||||
artistIDs?: string;
|
||||
@@ -270,7 +290,9 @@ const semantics = Joi.object().keys({
|
||||
venueEntrance: Joi.string(),
|
||||
venuePhoneNumber: Joi.string(),
|
||||
venueRoom: Joi.string(),
|
||||
eventType: Joi.string().regex(/(PKEventTypeGeneric|PKEventTypeLivePerformance|PKEventTypeMovie|PKEventTypeSports|PKEventTypeConference|PKEventTypeConvention|PKEventTypeWorkshop|PKEventTypeSocialGathering)/),
|
||||
eventType: Joi.string().regex(
|
||||
/(PKEventTypeGeneric|PKEventTypeLivePerformance|PKEventTypeMovie|PKEventTypeSports|PKEventTypeConference|PKEventTypeConvention|PKEventTypeWorkshop|PKEventTypeSocialGathering)/,
|
||||
),
|
||||
eventStartDate: Joi.string(),
|
||||
eventEndDate: Joi.string(),
|
||||
artistIDs: Joi.string(),
|
||||
@@ -287,7 +309,7 @@ const semantics = Joi.object().keys({
|
||||
awayTeamAbbreviation: Joi.string(),
|
||||
sportName: Joi.string(),
|
||||
// Store Card Passes
|
||||
balance: currencyAmount
|
||||
balance: currencyAmount,
|
||||
});
|
||||
|
||||
export interface ValidPassType {
|
||||
@@ -310,11 +332,16 @@ interface PassInterfacesProps {
|
||||
voided?: boolean;
|
||||
}
|
||||
|
||||
type AllPassProps = PassInterfacesProps & ValidPassType & OverridesSupportedOptions;
|
||||
type AllPassProps = PassInterfacesProps &
|
||||
ValidPassType &
|
||||
OverridesSupportedOptions;
|
||||
export type ValidPass = {
|
||||
[K in keyof AllPassProps]: AllPassProps[K];
|
||||
};
|
||||
export type PassColors = Pick<OverridesSupportedOptions, "backgroundColor" | "foregroundColor" | "labelColor">;
|
||||
export type PassColors = Pick<
|
||||
OverridesSupportedOptions,
|
||||
"backgroundColor" | "foregroundColor" | "labelColor"
|
||||
>;
|
||||
|
||||
export interface Barcode {
|
||||
altText?: string;
|
||||
@@ -323,13 +350,22 @@ export interface Barcode {
|
||||
message: string;
|
||||
}
|
||||
|
||||
export type BarcodeFormat = "PKBarcodeFormatQR" | "PKBarcodeFormatPDF417" | "PKBarcodeFormatAztec" | "PKBarcodeFormatCode128";
|
||||
export type BarcodeFormat =
|
||||
| "PKBarcodeFormatQR"
|
||||
| "PKBarcodeFormatPDF417"
|
||||
| "PKBarcodeFormatAztec"
|
||||
| "PKBarcodeFormatCode128";
|
||||
|
||||
const barcode = Joi.object().keys({
|
||||
altText: Joi.string(),
|
||||
messageEncoding: Joi.string().default("iso-8859-1"),
|
||||
format: Joi.string().required().regex(/(PKBarcodeFormatQR|PKBarcodeFormatPDF417|PKBarcodeFormatAztec|PKBarcodeFormatCode128)/, "barcodeType"),
|
||||
message: Joi.string().required()
|
||||
format: Joi.string()
|
||||
.required()
|
||||
.regex(
|
||||
/(PKBarcodeFormatQR|PKBarcodeFormatPDF417|PKBarcodeFormatAztec|PKBarcodeFormatCode128)/,
|
||||
"barcodeType",
|
||||
),
|
||||
message: Joi.string().required(),
|
||||
});
|
||||
|
||||
export interface Field {
|
||||
@@ -350,30 +386,53 @@ export interface Field {
|
||||
}
|
||||
|
||||
const field = Joi.object().keys({
|
||||
attributedValue: Joi.alternatives(Joi.string().allow(""), Joi.number(), Joi.date().iso()),
|
||||
attributedValue: Joi.alternatives(
|
||||
Joi.string().allow(""),
|
||||
Joi.number(),
|
||||
Joi.date().iso(),
|
||||
),
|
||||
changeMessage: Joi.string(),
|
||||
dataDetectorType: Joi.array().items(Joi.string().regex(/(PKDataDetectorTypePhoneNumber|PKDataDetectorTypeLink|PKDataDetectorTypeAddress|PKDataDetectorTypeCalendarEvent)/, "dataDetectorType")),
|
||||
dataDetectorType: Joi.array().items(
|
||||
Joi.string().regex(
|
||||
/(PKDataDetectorTypePhoneNumber|PKDataDetectorTypeLink|PKDataDetectorTypeAddress|PKDataDetectorTypeCalendarEvent)/,
|
||||
"dataDetectorType",
|
||||
),
|
||||
),
|
||||
label: Joi.string().allow(""),
|
||||
textAlignment: Joi.string().regex(/(PKTextAlignmentLeft|PKTextAlignmentCenter|PKTextAlignmentRight|PKTextAlignmentNatural)/, "graphic-alignment"),
|
||||
textAlignment: Joi.string().regex(
|
||||
/(PKTextAlignmentLeft|PKTextAlignmentCenter|PKTextAlignmentRight|PKTextAlignmentNatural)/,
|
||||
"graphic-alignment",
|
||||
),
|
||||
key: Joi.string().required(),
|
||||
value: Joi.alternatives(Joi.string().allow(""), Joi.number(), Joi.date().iso()).required(),
|
||||
value: Joi.alternatives(
|
||||
Joi.string().allow(""),
|
||||
Joi.number(),
|
||||
Joi.date().iso(),
|
||||
).required(),
|
||||
semantics,
|
||||
// date fields formatters, all optionals
|
||||
dateStyle: Joi.string().regex(/(PKDateStyleNone|PKDateStyleShort|PKDateStyleMedium|PKDateStyleLong|PKDateStyleFull)/, "date style"),
|
||||
dateStyle: Joi.string().regex(
|
||||
/(PKDateStyleNone|PKDateStyleShort|PKDateStyleMedium|PKDateStyleLong|PKDateStyleFull)/,
|
||||
"date style",
|
||||
),
|
||||
ignoreTimeZone: Joi.boolean(),
|
||||
isRelative: Joi.boolean(),
|
||||
timeStyle: Joi.string().regex(/(PKDateStyleNone|PKDateStyleShort|PKDateStyleMedium|PKDateStyleLong|PKDateStyleFull)/, "date style"),
|
||||
timeStyle: Joi.string().regex(
|
||||
/(PKDateStyleNone|PKDateStyleShort|PKDateStyleMedium|PKDateStyleLong|PKDateStyleFull)/,
|
||||
"date style",
|
||||
),
|
||||
// number fields formatters, all optionals
|
||||
currencyCode: Joi.string()
|
||||
.when("value", {
|
||||
currencyCode: Joi.string().when("value", {
|
||||
is: Joi.number(),
|
||||
otherwise: Joi.string().forbidden()
|
||||
otherwise: Joi.string().forbidden(),
|
||||
}),
|
||||
numberStyle: Joi.string()
|
||||
.regex(/(PKNumberStyleDecimal|PKNumberStylePercent|PKNumberStyleScientific|PKNumberStyleSpellOut)/)
|
||||
.regex(
|
||||
/(PKNumberStyleDecimal|PKNumberStylePercent|PKNumberStyleScientific|PKNumberStyleSpellOut)/,
|
||||
)
|
||||
.when("value", {
|
||||
is: Joi.number(),
|
||||
otherwise: Joi.string().forbidden()
|
||||
otherwise: Joi.string().forbidden(),
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -385,10 +444,14 @@ export interface Beacon {
|
||||
}
|
||||
|
||||
const beaconsDict = Joi.object().keys({
|
||||
major: Joi.number().integer().positive().max(65535).greater(Joi.ref("minor")),
|
||||
major: Joi.number()
|
||||
.integer()
|
||||
.positive()
|
||||
.max(65535)
|
||||
.greater(Joi.ref("minor")),
|
||||
minor: Joi.number().integer().min(0).max(65535),
|
||||
proximityUUID: Joi.string().required(),
|
||||
relevantText: Joi.string()
|
||||
relevantText: Joi.string(),
|
||||
});
|
||||
|
||||
export interface Location {
|
||||
@@ -402,7 +465,7 @@ const locationsDict = Joi.object().keys({
|
||||
altitude: Joi.number(),
|
||||
latitude: Joi.number().required(),
|
||||
longitude: Joi.number().required(),
|
||||
relevantText: Joi.string()
|
||||
relevantText: Joi.string(),
|
||||
});
|
||||
|
||||
export interface PassFields {
|
||||
@@ -414,18 +477,29 @@ export interface PassFields {
|
||||
}
|
||||
|
||||
const passDict = Joi.object().keys({
|
||||
auxiliaryFields: Joi.array().items(Joi.object().keys({
|
||||
row: Joi.number().max(1).min(0)
|
||||
}).concat(field)),
|
||||
auxiliaryFields: Joi.array().items(
|
||||
Joi.object()
|
||||
.keys({
|
||||
row: Joi.number().max(1).min(0),
|
||||
})
|
||||
.concat(field),
|
||||
),
|
||||
backFields: Joi.array().items(field),
|
||||
headerFields: Joi.array().items(field),
|
||||
primaryFields: Joi.array().items(field),
|
||||
secondaryFields: Joi.array().items(field)
|
||||
secondaryFields: Joi.array().items(field),
|
||||
});
|
||||
|
||||
export type TransitType = "PKTransitTypeAir" | "PKTransitTypeBoat" | "PKTransitTypeBus" | "PKTransitTypeGeneric" | "PKTransitTypeTrain";
|
||||
export type TransitType =
|
||||
| "PKTransitTypeAir"
|
||||
| "PKTransitTypeBoat"
|
||||
| "PKTransitTypeBus"
|
||||
| "PKTransitTypeGeneric"
|
||||
| "PKTransitTypeTrain";
|
||||
|
||||
const transitType = Joi.string().regex(/(PKTransitTypeAir|PKTransitTypeBoat|PKTransitTypeBus|PKTransitTypeGeneric|PKTransitTypeTrain)/);
|
||||
const transitType = Joi.string().regex(
|
||||
/(PKTransitTypeAir|PKTransitTypeBoat|PKTransitTypeBus|PKTransitTypeGeneric|PKTransitTypeTrain)/,
|
||||
);
|
||||
|
||||
export interface NFC {
|
||||
message: string;
|
||||
@@ -434,7 +508,7 @@ export interface NFC {
|
||||
|
||||
const nfcDict = Joi.object().keys({
|
||||
message: Joi.string().required().max(64),
|
||||
encryptionPublicKey: Joi.string()
|
||||
encryptionPublicKey: Joi.string(),
|
||||
});
|
||||
|
||||
// ************************************* //
|
||||
@@ -447,11 +521,20 @@ export interface Personalization {
|
||||
termsAndConditions?: string;
|
||||
}
|
||||
|
||||
type PRSField = "PKPassPersonalizationFieldName" | "PKPassPersonalizationFieldPostalCode" | "PKPassPersonalizationFieldEmailAddress" | "PKPassPersonalizationFieldPhoneNumber";
|
||||
type PRSField =
|
||||
| "PKPassPersonalizationFieldName"
|
||||
| "PKPassPersonalizationFieldPostalCode"
|
||||
| "PKPassPersonalizationFieldEmailAddress"
|
||||
| "PKPassPersonalizationFieldPhoneNumber";
|
||||
|
||||
const personalizationDict = Joi.object().keys({
|
||||
requiredPersonalizationFields: Joi.array()
|
||||
.items("PKPassPersonalizationFieldName", "PKPassPersonalizationFieldPostalCode", "PKPassPersonalizationFieldEmailAddress", "PKPassPersonalizationFieldPhoneNumber")
|
||||
.items(
|
||||
"PKPassPersonalizationFieldName",
|
||||
"PKPassPersonalizationFieldPostalCode",
|
||||
"PKPassPersonalizationFieldEmailAddress",
|
||||
"PKPassPersonalizationFieldPhoneNumber",
|
||||
)
|
||||
.required(),
|
||||
description: Joi.string().required(),
|
||||
termsAndConditions: Joi.string(),
|
||||
@@ -470,7 +553,7 @@ const schemas = {
|
||||
transitType,
|
||||
nfcDict,
|
||||
supportedOptions,
|
||||
personalizationDict
|
||||
personalizationDict,
|
||||
};
|
||||
|
||||
export type Schema = keyof typeof schemas;
|
||||
@@ -491,14 +574,18 @@ export function isValid(opts: any, schemaName: Schema): boolean {
|
||||
const resolvedSchema = resolveSchemaName(schemaName);
|
||||
|
||||
if (!resolvedSchema) {
|
||||
schemaDebug(`validation failed due to missing or mispelled schema name`);
|
||||
schemaDebug(
|
||||
`validation failed due to missing or mispelled schema name`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
const validation = resolvedSchema.validate(opts);
|
||||
|
||||
if (validation.error) {
|
||||
schemaDebug(`validation failed due to error: ${validation.error.message}`);
|
||||
schemaDebug(
|
||||
`validation failed due to error: ${validation.error.message}`,
|
||||
);
|
||||
}
|
||||
|
||||
return !validation.error;
|
||||
@@ -511,18 +598,25 @@ export function isValid(opts: any, schemaName: Schema): boolean {
|
||||
* @returns {object} the filtered value or empty object
|
||||
*/
|
||||
|
||||
export function getValidated<T extends Object>(opts: any, schemaName: Schema): T | null {
|
||||
export function getValidated<T extends Object>(
|
||||
opts: any,
|
||||
schemaName: Schema,
|
||||
): T | null {
|
||||
const resolvedSchema = resolveSchemaName(schemaName);
|
||||
|
||||
if (!resolvedSchema) {
|
||||
schemaDebug(`validation failed due to missing or mispelled schema name`);
|
||||
schemaDebug(
|
||||
`validation failed due to missing or mispelled schema name`,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
const validation = resolvedSchema.validate(opts, { stripUnknown: true });
|
||||
|
||||
if (validation.error) {
|
||||
schemaDebug(`Validation failed in getValidated due to error: ${validation.error.message}`);
|
||||
schemaDebug(
|
||||
`Validation failed in getValidated due to error: ${validation.error.message}`,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
65
src/utils.ts
65
src/utils.ts
@@ -16,13 +16,15 @@ export function isValidRGB(value?: string): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
const rgb = value.match(/^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)/);
|
||||
const rgb = value.match(
|
||||
/^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)/,
|
||||
);
|
||||
|
||||
if (!rgb) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return rgb.slice(1, 4).every(v => Math.abs(Number(v)) <= 255);
|
||||
return rgb.slice(1, 4).every((v) => Math.abs(Number(v)) <= 255);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,7 +59,7 @@ export function dateToW3CString(date: Date) {
|
||||
*/
|
||||
|
||||
export function removeHidden(from: Array<string>): Array<string> {
|
||||
return from.filter(e => e.charAt(0) !== ".");
|
||||
return from.filter((e) => e.charAt(0) !== ".");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,8 +79,9 @@ export function generateStringFile(lang: { [index: string]: string }): Buffer {
|
||||
// Pass.strings format is the following one for each row:
|
||||
// "key" = "value";
|
||||
|
||||
const strings = Object.keys(lang)
|
||||
.map(key => `"${key}" = "${lang[key].replace(/"/g, '\"')}";`);
|
||||
const strings = Object.keys(lang).map(
|
||||
(key) => `"${key}" = "${lang[key].replace(/"/g, '"')}";`,
|
||||
);
|
||||
|
||||
return Buffer.from(strings.join(EOL), "utf8");
|
||||
}
|
||||
@@ -89,23 +92,29 @@ export function generateStringFile(lang: { [index: string]: string }): Buffer {
|
||||
* @param origin
|
||||
*/
|
||||
|
||||
type PartitionedBundleElements = [PartitionedBundle["l10nBundle"], PartitionedBundle["bundle"]];
|
||||
type PartitionedBundleElements = [
|
||||
PartitionedBundle["l10nBundle"],
|
||||
PartitionedBundle["bundle"],
|
||||
];
|
||||
|
||||
export function splitBufferBundle(origin: BundleUnit): PartitionedBundleElements {
|
||||
export function splitBufferBundle(
|
||||
origin: BundleUnit,
|
||||
): PartitionedBundleElements {
|
||||
const initialValue: PartitionedBundleElements = [{}, {}];
|
||||
|
||||
if (!origin) {
|
||||
return initialValue;
|
||||
}
|
||||
|
||||
return Object.entries(origin).reduce<PartitionedBundleElements>(([l10n, bundle], [key, buffer]) => {
|
||||
return Object.entries(origin).reduce<PartitionedBundleElements>(
|
||||
([l10n, bundle], [key, buffer]) => {
|
||||
if (!key.includes(".lproj")) {
|
||||
return [
|
||||
l10n,
|
||||
{
|
||||
...bundle,
|
||||
[key]: buffer
|
||||
}
|
||||
[key]: buffer,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -116,20 +125,40 @@ export function splitBufferBundle(origin: BundleUnit): PartitionedBundleElements
|
||||
(l10n[lang] || (l10n[lang] = {}))[file] = buffer;
|
||||
|
||||
return [l10n, bundle];
|
||||
}, initialValue);
|
||||
},
|
||||
initialValue,
|
||||
);
|
||||
}
|
||||
|
||||
type StringSearchMode = "includes" | "startsWith" | "endsWith";
|
||||
|
||||
export function getAllFilesWithName(name: string, source: string[], mode: StringSearchMode = "includes", forceLowerCase: boolean = false): string[] {
|
||||
return source.filter(file => (forceLowerCase && file.toLowerCase() || file)[mode](name));
|
||||
export function getAllFilesWithName(
|
||||
name: string,
|
||||
source: string[],
|
||||
mode: StringSearchMode = "includes",
|
||||
forceLowerCase: boolean = false,
|
||||
): string[] {
|
||||
return source.filter((file) =>
|
||||
((forceLowerCase && file.toLowerCase()) || file)[mode](name),
|
||||
);
|
||||
}
|
||||
|
||||
export function hasFilesWithName(name: string, source: string[], mode: StringSearchMode = "includes", forceLowerCase: boolean = false): boolean {
|
||||
return source.some(file => (forceLowerCase && file.toLowerCase() || file)[mode](name));
|
||||
export function hasFilesWithName(
|
||||
name: string,
|
||||
source: string[],
|
||||
mode: StringSearchMode = "includes",
|
||||
forceLowerCase: boolean = false,
|
||||
): boolean {
|
||||
return source.some((file) =>
|
||||
((forceLowerCase && file.toLowerCase()) || file)[mode](name),
|
||||
);
|
||||
}
|
||||
|
||||
export function deletePersonalization(source: BundleUnit, logosNames: string[] = []): void {
|
||||
[...logosNames, "personalization.json"]
|
||||
.forEach(file => delete source[file]);
|
||||
export function deletePersonalization(
|
||||
source: BundleUnit,
|
||||
logosNames: string[] = [],
|
||||
): void {
|
||||
[...logosNames, "personalization.json"].forEach(
|
||||
(file) => delete source[file],
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user