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/
|
passModels/
|
||||||
certificates/
|
certificates/
|
||||||
*.code-workspace
|
*.code-workspace
|
||||||
.vscode/
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
*.js
|
*.js
|
||||||
lib/
|
lib/
|
||||||
examples/build
|
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.
|
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.
|
Express.js has been inserted as "example package" dipendency.
|
||||||
|
|
||||||
```sh
|
```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.
|
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.
|
Please note that `field.js` example will force you to download `exampleBooking.pass`, no matter what.
|
||||||
___
|
|
||||||
|
---
|
||||||
|
|
||||||
Every contribution is really appreciated. ❤️ Thank you!
|
Every contribution is really appreciated. ❤️ Thank you!
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import genRoute, { app } from "./webserver";
|
import genRoute, { app } from "./webserver";
|
||||||
import { createPass, createAbstractModel, AbstractModel } from "passkit-generator";
|
import {
|
||||||
|
createPass,
|
||||||
|
createAbstractModel,
|
||||||
|
AbstractModel,
|
||||||
|
} from "passkit-generator";
|
||||||
|
|
||||||
let abstractModel: AbstractModel;
|
let abstractModel: AbstractModel;
|
||||||
|
|
||||||
@@ -11,148 +15,182 @@ let abstractModel: AbstractModel;
|
|||||||
signerCert: "../certificates/signerCert.pem",
|
signerCert: "../certificates/signerCert.pem",
|
||||||
signerKey: {
|
signerKey: {
|
||||||
keyFile: "../certificates/signerKey.pem",
|
keyFile: "../certificates/signerKey.pem",
|
||||||
passphrase: "123456"
|
passphrase: "123456",
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
// overrides: request.body || request.params || request.query,
|
// overrides: request.body || request.params || request.query,
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|
||||||
genRoute.all(async function manageRequest(request, response) {
|
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 {
|
try {
|
||||||
const pass = await createPass(abstractModel);
|
const pass = await createPass(abstractModel);
|
||||||
|
|
||||||
pass.transitType = "PKTransitTypeAir";
|
pass.transitType = "PKTransitTypeAir";
|
||||||
|
|
||||||
pass.headerFields.push({
|
pass.headerFields.push(
|
||||||
"key": "header1",
|
{
|
||||||
"label": "Data",
|
key: "header1",
|
||||||
"value": "25 mag",
|
label: "Data",
|
||||||
"textAlignment": "PKTextAlignmentCenter"
|
value: "25 mag",
|
||||||
}, {
|
textAlignment: "PKTextAlignmentCenter",
|
||||||
"key": "header2",
|
},
|
||||||
"label": "Volo",
|
{
|
||||||
"value": "EZY997",
|
key: "header2",
|
||||||
"textAlignment": "PKTextAlignmentCenter"
|
label: "Volo",
|
||||||
});
|
value: "EZY997",
|
||||||
|
textAlignment: "PKTextAlignmentCenter",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
pass.primaryFields.push({
|
pass.primaryFields.push(
|
||||||
key: "IATA-source",
|
{
|
||||||
value: "NAP",
|
key: "IATA-source",
|
||||||
label: "Napoli",
|
value: "NAP",
|
||||||
textAlignment: "PKTextAlignmentLeft"
|
label: "Napoli",
|
||||||
}, {
|
textAlignment: "PKTextAlignmentLeft",
|
||||||
key: "IATA-destination",
|
},
|
||||||
value: "VCE",
|
{
|
||||||
label: "Venezia Marco Polo",
|
key: "IATA-destination",
|
||||||
textAlignment: "PKTextAlignmentRight"
|
value: "VCE",
|
||||||
});
|
label: "Venezia Marco Polo",
|
||||||
|
textAlignment: "PKTextAlignmentRight",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
pass.secondaryFields.push({
|
pass.secondaryFields.push(
|
||||||
"key": "secondary1",
|
{
|
||||||
"label": "Imbarco chiuso",
|
key: "secondary1",
|
||||||
"value": "18:40",
|
label: "Imbarco chiuso",
|
||||||
"textAlignment": "PKTextAlignmentCenter",
|
value: "18:40",
|
||||||
}, {
|
textAlignment: "PKTextAlignmentCenter",
|
||||||
"key": "sec2",
|
},
|
||||||
"label": "Partenze",
|
{
|
||||||
"value": "19:10",
|
key: "sec2",
|
||||||
"textAlignment": "PKTextAlignmentCenter"
|
label: "Partenze",
|
||||||
}, {
|
value: "19:10",
|
||||||
"key": "sec3",
|
textAlignment: "PKTextAlignmentCenter",
|
||||||
"label": "SB",
|
},
|
||||||
"value": "Sì",
|
{
|
||||||
"textAlignment": "PKTextAlignmentCenter"
|
key: "sec3",
|
||||||
}, {
|
label: "SB",
|
||||||
"key": "sec4",
|
value: "Sì",
|
||||||
"label": "Imbarco",
|
textAlignment: "PKTextAlignmentCenter",
|
||||||
"value": "Anteriore",
|
},
|
||||||
"textAlignment": "PKTextAlignmentCenter"
|
{
|
||||||
});
|
key: "sec4",
|
||||||
|
label: "Imbarco",
|
||||||
|
value: "Anteriore",
|
||||||
|
textAlignment: "PKTextAlignmentCenter",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
pass.auxiliaryFields.push({
|
pass.auxiliaryFields.push(
|
||||||
"key": "aux1",
|
{
|
||||||
"label": "Passeggero",
|
key: "aux1",
|
||||||
"value": "MR. WHO KNOWS",
|
label: "Passeggero",
|
||||||
"textAlignment": "PKTextAlignmentLeft"
|
value: "MR. WHO KNOWS",
|
||||||
}, {
|
textAlignment: "PKTextAlignmentLeft",
|
||||||
"key": "aux2",
|
},
|
||||||
"label": "Posto",
|
{
|
||||||
"value": "1A*",
|
key: "aux2",
|
||||||
"textAlignment": "PKTextAlignmentCenter"
|
label: "Posto",
|
||||||
});
|
value: "1A*",
|
||||||
|
textAlignment: "PKTextAlignmentCenter",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
pass.backFields.push({
|
pass.backFields.push(
|
||||||
"key": "document number",
|
{
|
||||||
"label": "Numero documento:",
|
key: "document number",
|
||||||
"value": "- -",
|
label: "Numero documento:",
|
||||||
"textAlignment": "PKTextAlignmentLeft"
|
value: "- -",
|
||||||
}, {
|
textAlignment: "PKTextAlignmentLeft",
|
||||||
"key": "You're checked in, what next",
|
},
|
||||||
"label": "Hai effettuato il check-in, Quali sono le prospettive",
|
{
|
||||||
"value": "",
|
key: "You're checked in, what next",
|
||||||
"textAlignment": "PKTextAlignmentLeft"
|
label: "Hai effettuato il check-in, Quali sono le prospettive",
|
||||||
}, {
|
value: "",
|
||||||
"key": "Check In",
|
textAlignment: "PKTextAlignmentLeft",
|
||||||
"label": "1. check-in✓",
|
},
|
||||||
"value": "",
|
{
|
||||||
"textAlignment": "PKTextAlignmentLeft"
|
key: "Check In",
|
||||||
}, {
|
label: "1. check-in✓",
|
||||||
"key": "checkIn",
|
value: "",
|
||||||
"label": "",
|
textAlignment: "PKTextAlignmentLeft",
|
||||||
"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: "checkIn",
|
||||||
"key": "2. Bags",
|
label: "",
|
||||||
"label": "2. Bagaglio",
|
value:
|
||||||
"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"
|
textAlignment: "PKTextAlignmentLeft",
|
||||||
}, {
|
},
|
||||||
"key": "Require special assistance",
|
{
|
||||||
"label": "Assistenza speciale",
|
key: "2. Bags",
|
||||||
"value": "Se hai richiesto assistenza speciale, presentati a un membro del personale nell'area di Consegna bagagli almeno 90 minuti prima del volo.",
|
label: "2. Bagaglio",
|
||||||
"textAlignment": "PKTextAlignmentLeft"
|
value: "",
|
||||||
}, {
|
textAlignment: "PKTextAlignmentLeft",
|
||||||
"key": "3. Departures",
|
},
|
||||||
"label": "3. Partenze",
|
{
|
||||||
"value": "",
|
key: "Require special assistance",
|
||||||
"textAlignment": "PKTextAlignmentLeft"
|
label: "Assistenza speciale",
|
||||||
}, {
|
value:
|
||||||
"key": "photoId",
|
"Se hai richiesto assistenza speciale, presentati a un membro del personale nell'area di Consegna bagagli almeno 90 minuti prima del volo.",
|
||||||
"label": "Un documento d’identità corredato di fotografia",
|
textAlignment: "PKTextAlignmentLeft",
|
||||||
"value": "è obbligatorio su TUTTI i voli. Per un viaggio internazionale è necessario un passaporto valido o, dove consentita, una carta d’identità.",
|
},
|
||||||
"textAlignment": "PKTextAlignmentLeft"
|
{
|
||||||
}, {
|
key: "3. Departures",
|
||||||
"key": "yourSeat",
|
label: "3. Partenze",
|
||||||
"label": "Il tuo posto:",
|
value: "",
|
||||||
"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",
|
||||||
"textAlignment": "PKTextAlignmentLeft"
|
},
|
||||||
}, {
|
{
|
||||||
"key": "Pack safely",
|
key: "photoId",
|
||||||
"label": "Bagaglio sicuro",
|
label: "Un documento d’identità corredato di fotografia",
|
||||||
"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",
|
value:
|
||||||
"textAlignment": "PKTextAlignmentLeft"
|
"è obbligatorio su TUTTI i voli. Per un viaggio internazionale è necessario un passaporto valido o, dove consentita, una carta d’identità.",
|
||||||
}, {
|
textAlignment: "PKTextAlignmentLeft",
|
||||||
"key": "Thank you for travelling easyJet",
|
},
|
||||||
"label": "Grazie per aver viaggiato con easyJet",
|
{
|
||||||
"value": "",
|
key: "yourSeat",
|
||||||
"textAlignment": "PKTextAlignmentLeft"
|
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();
|
const stream = pass.generate();
|
||||||
response.set({
|
response.set({
|
||||||
"Content-type": "application/vnd.apple.pkpass",
|
"Content-type": "application/vnd.apple.pkpass",
|
||||||
"Content-disposition": `attachment; filename=${passName}.pkpass`
|
"Content-disposition": `attachment; filename=${passName}.pkpass`,
|
||||||
});
|
});
|
||||||
|
|
||||||
stream.pipe(response);
|
stream.pipe(response);
|
||||||
} catch(err) {
|
} catch (err) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
|
|
||||||
response.set({
|
response.set({
|
||||||
"Content-type": "text/html"
|
"Content-type": "text/html",
|
||||||
});
|
});
|
||||||
|
|
||||||
response.send(err.message);
|
response.send(err.message);
|
||||||
|
|||||||
@@ -11,12 +11,14 @@ import fetch from "node-fetch";
|
|||||||
import { createPass } from "passkit-generator";
|
import { createPass } from "passkit-generator";
|
||||||
|
|
||||||
app.all(async function manageRequest(request, response) {
|
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 (
|
const avatar = await fetch(
|
||||||
fetch("https://s.gravatar.com/avatar/83cd11399b7ea79977bc302f3931ee52?size=32&default=retro")
|
"https://s.gravatar.com/avatar/83cd11399b7ea79977bc302f3931ee52?size=32&default=retro",
|
||||||
.then(res => res.buffer())
|
).then((res) => res.buffer());
|
||||||
);
|
|
||||||
|
|
||||||
const passConfig = {
|
const passConfig = {
|
||||||
model: `./models/${request.params.modelName}`,
|
model: `./models/${request.params.modelName}`,
|
||||||
@@ -25,8 +27,8 @@ app.all(async function manageRequest(request, response) {
|
|||||||
signerCert: "../certificates/signerCert.pem",
|
signerCert: "../certificates/signerCert.pem",
|
||||||
signerKey: {
|
signerKey: {
|
||||||
keyFile: "../certificates/signerKey.pem",
|
keyFile: "../certificates/signerKey.pem",
|
||||||
passphrase: "123456"
|
passphrase: "123456",
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
overrides: request.body || request.params || request.query,
|
overrides: request.body || request.params || request.query,
|
||||||
};
|
};
|
||||||
@@ -44,7 +46,7 @@ app.all(async function manageRequest(request, response) {
|
|||||||
|
|
||||||
response.set({
|
response.set({
|
||||||
"Content-type": "application/vnd.apple.pkpass",
|
"Content-type": "application/vnd.apple.pkpass",
|
||||||
"Content-disposition": `attachment; filename=${passName}.pkpass`
|
"Content-disposition": `attachment; filename=${passName}.pkpass`,
|
||||||
});
|
});
|
||||||
|
|
||||||
stream.pipe(response);
|
stream.pipe(response);
|
||||||
|
|||||||
@@ -12,7 +12,10 @@ import app from "./webserver";
|
|||||||
import { createPass } from "passkit-generator";
|
import { createPass } from "passkit-generator";
|
||||||
|
|
||||||
app.all(async function manageRequest(request, response) {
|
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 {
|
try {
|
||||||
const pass = await createPass({
|
const pass = await createPass({
|
||||||
@@ -22,8 +25,8 @@ app.all(async function manageRequest(request, response) {
|
|||||||
signerCert: "../certificates/signerCert.pem",
|
signerCert: "../certificates/signerCert.pem",
|
||||||
signerKey: {
|
signerKey: {
|
||||||
keyFile: "../certificates/signerKey.pem",
|
keyFile: "../certificates/signerKey.pem",
|
||||||
passphrase: "123456"
|
passphrase: "123456",
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
overrides: request.body || request.params || request.query,
|
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
|
// After this, pass.props["barcodes"] will have support for just two of three
|
||||||
// of the passed format (the valid ones);
|
// of the passed format (the valid ones);
|
||||||
|
|
||||||
pass.barcodes({
|
pass.barcodes(
|
||||||
message: "Thank you for using this package <3",
|
{
|
||||||
format: "PKBarcodeFormatCode128"
|
message: "Thank you for using this package <3",
|
||||||
}, {
|
format: "PKBarcodeFormatCode128",
|
||||||
message: "Thank you for using this package <3",
|
},
|
||||||
format: "PKBarcodeFormatPDF417"
|
{
|
||||||
}, {
|
message: "Thank you for using this package <3",
|
||||||
message: "Thank you for using this package <3",
|
format: "PKBarcodeFormatPDF417",
|
||||||
// @ts-expect-error
|
},
|
||||||
format: "PKBarcodeFormatMock44617"
|
{
|
||||||
});
|
message: "Thank you for using this package <3",
|
||||||
|
// @ts-expect-error
|
||||||
|
format: "PKBarcodeFormatMock44617",
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// You can change the format chosen for barcode prop support by calling .barcode()
|
// 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");
|
pass.barcode("PKBarcodeFormatPDF417");
|
||||||
|
|
||||||
console.log("Barcode property is now:", pass.props["barcode"]);
|
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();
|
const stream = pass.generate();
|
||||||
response.set({
|
response.set({
|
||||||
"Content-type": "application/vnd.apple.pkpass",
|
"Content-type": "application/vnd.apple.pkpass",
|
||||||
"Content-disposition": `attachment; filename=${passName}.pkpass`
|
"Content-disposition": `attachment; filename=${passName}.pkpass`,
|
||||||
});
|
});
|
||||||
|
|
||||||
stream.pipe(response);
|
stream.pipe(response);
|
||||||
|
|||||||
@@ -13,11 +13,16 @@ import { createPass } from "passkit-generator";
|
|||||||
|
|
||||||
app.all(async function manageRequest(request, response) {
|
app.all(async function manageRequest(request, response) {
|
||||||
if (!request.query.fn) {
|
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;
|
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 {
|
try {
|
||||||
let pass = await createPass({
|
let pass = await createPass({
|
||||||
@@ -27,8 +32,8 @@ app.all(async function manageRequest(request, response) {
|
|||||||
signerCert: "../certificates/signerCert.pem",
|
signerCert: "../certificates/signerCert.pem",
|
||||||
signerKey: {
|
signerKey: {
|
||||||
keyFile: "../certificates/signerKey.pem",
|
keyFile: "../certificates/signerKey.pem",
|
||||||
passphrase: "123456"
|
passphrase: "123456",
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
overrides: request.body || request.params || request.query,
|
overrides: request.body || request.params || request.query,
|
||||||
});
|
});
|
||||||
@@ -47,7 +52,7 @@ app.all(async function manageRequest(request, response) {
|
|||||||
const stream = pass.generate();
|
const stream = pass.generate();
|
||||||
response.set({
|
response.set({
|
||||||
"Content-type": "application/vnd.apple.pkpass",
|
"Content-type": "application/vnd.apple.pkpass",
|
||||||
"Content-disposition": `attachment; filename=${passName}.pkpass`
|
"Content-disposition": `attachment; filename=${passName}.pkpass`,
|
||||||
});
|
});
|
||||||
|
|
||||||
stream.pipe(response);
|
stream.pipe(response);
|
||||||
|
|||||||
@@ -13,7 +13,10 @@ import app from "./webserver";
|
|||||||
import { createPass } from "passkit-generator";
|
import { createPass } from "passkit-generator";
|
||||||
|
|
||||||
app.all(async function manageRequest(request, response) {
|
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 {
|
try {
|
||||||
let pass = await createPass({
|
let pass = await createPass({
|
||||||
model: `./models/exampleBooking`,
|
model: `./models/exampleBooking`,
|
||||||
@@ -22,133 +25,164 @@ app.all(async function manageRequest(request, response) {
|
|||||||
signerCert: "../certificates/signerCert.pem",
|
signerCert: "../certificates/signerCert.pem",
|
||||||
signerKey: {
|
signerKey: {
|
||||||
keyFile: "../certificates/signerKey.pem",
|
keyFile: "../certificates/signerKey.pem",
|
||||||
passphrase: "123456"
|
passphrase: "123456",
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
overrides: request.body || request.params || request.query,
|
overrides: request.body || request.params || request.query,
|
||||||
});
|
});
|
||||||
|
|
||||||
pass.transitType = "PKTransitTypeAir";
|
pass.transitType = "PKTransitTypeAir";
|
||||||
|
|
||||||
pass.headerFields.push({
|
pass.headerFields.push(
|
||||||
"key": "header1",
|
{
|
||||||
"label": "Data",
|
key: "header1",
|
||||||
"value": "25 mag",
|
label: "Data",
|
||||||
"textAlignment": "PKTextAlignmentCenter"
|
value: "25 mag",
|
||||||
}, {
|
textAlignment: "PKTextAlignmentCenter",
|
||||||
"key": "header2",
|
},
|
||||||
"label": "Volo",
|
{
|
||||||
"value": "EZY997",
|
key: "header2",
|
||||||
"textAlignment": "PKTextAlignmentCenter"
|
label: "Volo",
|
||||||
});
|
value: "EZY997",
|
||||||
|
textAlignment: "PKTextAlignmentCenter",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
pass.primaryFields.push({
|
pass.primaryFields.push(
|
||||||
key: "IATA-source",
|
{
|
||||||
value: "NAP",
|
key: "IATA-source",
|
||||||
label: "Napoli",
|
value: "NAP",
|
||||||
textAlignment: "PKTextAlignmentLeft"
|
label: "Napoli",
|
||||||
}, {
|
textAlignment: "PKTextAlignmentLeft",
|
||||||
key: "IATA-destination",
|
},
|
||||||
value: "VCE",
|
{
|
||||||
label: "Venezia Marco Polo",
|
key: "IATA-destination",
|
||||||
textAlignment: "PKTextAlignmentRight"
|
value: "VCE",
|
||||||
});
|
label: "Venezia Marco Polo",
|
||||||
|
textAlignment: "PKTextAlignmentRight",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
pass.secondaryFields.push({
|
pass.secondaryFields.push(
|
||||||
"key": "secondary1",
|
{
|
||||||
"label": "Imbarco chiuso",
|
key: "secondary1",
|
||||||
"value": "18:40",
|
label: "Imbarco chiuso",
|
||||||
"textAlignment": "PKTextAlignmentCenter",
|
value: "18:40",
|
||||||
}, {
|
textAlignment: "PKTextAlignmentCenter",
|
||||||
"key": "sec2",
|
},
|
||||||
"label": "Partenze",
|
{
|
||||||
"value": "19:10",
|
key: "sec2",
|
||||||
"textAlignment": "PKTextAlignmentCenter"
|
label: "Partenze",
|
||||||
}, {
|
value: "19:10",
|
||||||
"key": "sec3",
|
textAlignment: "PKTextAlignmentCenter",
|
||||||
"label": "SB",
|
},
|
||||||
"value": "Sì",
|
{
|
||||||
"textAlignment": "PKTextAlignmentCenter"
|
key: "sec3",
|
||||||
}, {
|
label: "SB",
|
||||||
"key": "sec4",
|
value: "Sì",
|
||||||
"label": "Imbarco",
|
textAlignment: "PKTextAlignmentCenter",
|
||||||
"value": "Anteriore",
|
},
|
||||||
"textAlignment": "PKTextAlignmentCenter"
|
{
|
||||||
});
|
key: "sec4",
|
||||||
|
label: "Imbarco",
|
||||||
|
value: "Anteriore",
|
||||||
|
textAlignment: "PKTextAlignmentCenter",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
pass.auxiliaryFields.push({
|
pass.auxiliaryFields.push(
|
||||||
"key": "aux1",
|
{
|
||||||
"label": "Passeggero",
|
key: "aux1",
|
||||||
"value": "MR. WHO KNOWS",
|
label: "Passeggero",
|
||||||
"textAlignment": "PKTextAlignmentLeft"
|
value: "MR. WHO KNOWS",
|
||||||
}, {
|
textAlignment: "PKTextAlignmentLeft",
|
||||||
"key": "aux2",
|
},
|
||||||
"label": "Posto",
|
{
|
||||||
"value": "1A*",
|
key: "aux2",
|
||||||
"textAlignment": "PKTextAlignmentCenter"
|
label: "Posto",
|
||||||
});
|
value: "1A*",
|
||||||
|
textAlignment: "PKTextAlignmentCenter",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
pass.backFields.push({
|
pass.backFields.push(
|
||||||
"key": "document number",
|
{
|
||||||
"label": "Numero documento:",
|
key: "document number",
|
||||||
"value": "- -",
|
label: "Numero documento:",
|
||||||
"textAlignment": "PKTextAlignmentLeft"
|
value: "- -",
|
||||||
}, {
|
textAlignment: "PKTextAlignmentLeft",
|
||||||
"key": "You're checked in, what next",
|
},
|
||||||
"label": "Hai effettuato il check-in, Quali sono le prospettive",
|
{
|
||||||
"value": "",
|
key: "You're checked in, what next",
|
||||||
"textAlignment": "PKTextAlignmentLeft"
|
label: "Hai effettuato il check-in, Quali sono le prospettive",
|
||||||
}, {
|
value: "",
|
||||||
"key": "Check In",
|
textAlignment: "PKTextAlignmentLeft",
|
||||||
"label": "1. check-in✓",
|
},
|
||||||
"value": "",
|
{
|
||||||
"textAlignment": "PKTextAlignmentLeft"
|
key: "Check In",
|
||||||
}, {
|
label: "1. check-in✓",
|
||||||
"key": "checkIn",
|
value: "",
|
||||||
"label": "",
|
textAlignment: "PKTextAlignmentLeft",
|
||||||
"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: "checkIn",
|
||||||
"key": "2. Bags",
|
label: "",
|
||||||
"label": "2. Bagaglio",
|
value:
|
||||||
"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"
|
textAlignment: "PKTextAlignmentLeft",
|
||||||
}, {
|
},
|
||||||
"key": "Require special assistance",
|
{
|
||||||
"label": "Assistenza speciale",
|
key: "2. Bags",
|
||||||
"value": "Se hai richiesto assistenza speciale, presentati a un membro del personale nell'area di Consegna bagagli almeno 90 minuti prima del volo.",
|
label: "2. Bagaglio",
|
||||||
"textAlignment": "PKTextAlignmentLeft"
|
value: "",
|
||||||
}, {
|
textAlignment: "PKTextAlignmentLeft",
|
||||||
"key": "3. Departures",
|
},
|
||||||
"label": "3. Partenze",
|
{
|
||||||
"value": "",
|
key: "Require special assistance",
|
||||||
"textAlignment": "PKTextAlignmentLeft"
|
label: "Assistenza speciale",
|
||||||
}, {
|
value:
|
||||||
"key": "photoId",
|
"Se hai richiesto assistenza speciale, presentati a un membro del personale nell'area di Consegna bagagli almeno 90 minuti prima del volo.",
|
||||||
"label": "Un documento d’identità corredato di fotografia",
|
textAlignment: "PKTextAlignmentLeft",
|
||||||
"value": "è obbligatorio su TUTTI i voli. Per un viaggio internazionale è necessario un passaporto valido o, dove consentita, una carta d’identità.",
|
},
|
||||||
"textAlignment": "PKTextAlignmentLeft"
|
{
|
||||||
}, {
|
key: "3. Departures",
|
||||||
"key": "yourSeat",
|
label: "3. Partenze",
|
||||||
"label": "Il tuo posto:",
|
value: "",
|
||||||
"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",
|
||||||
"textAlignment": "PKTextAlignmentLeft"
|
},
|
||||||
}, {
|
{
|
||||||
"key": "Pack safely",
|
key: "photoId",
|
||||||
"label": "Bagaglio sicuro",
|
label: "Un documento d’identità corredato di fotografia",
|
||||||
"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",
|
value:
|
||||||
"textAlignment": "PKTextAlignmentLeft"
|
"è obbligatorio su TUTTI i voli. Per un viaggio internazionale è necessario un passaporto valido o, dove consentita, una carta d’identità.",
|
||||||
}, {
|
textAlignment: "PKTextAlignmentLeft",
|
||||||
"key": "Thank you for travelling easyJet",
|
},
|
||||||
"label": "Grazie per aver viaggiato con easyJet",
|
{
|
||||||
"value": "",
|
key: "yourSeat",
|
||||||
"textAlignment": "PKTextAlignmentLeft"
|
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();
|
const stream = pass.generate();
|
||||||
response.set({
|
response.set({
|
||||||
"Content-type": "application/vnd.apple.pkpass",
|
"Content-type": "application/vnd.apple.pkpass",
|
||||||
"Content-disposition": `attachment; filename=${passName}.pkpass`
|
"Content-disposition": `attachment; filename=${passName}.pkpass`,
|
||||||
});
|
});
|
||||||
|
|
||||||
stream.pipe(response);
|
stream.pipe(response);
|
||||||
@@ -156,7 +190,7 @@ app.all(async function manageRequest(request, response) {
|
|||||||
console.log(err);
|
console.log(err);
|
||||||
|
|
||||||
response.set({
|
response.set({
|
||||||
"Content-type": "text/html"
|
"Content-type": "text/html",
|
||||||
});
|
});
|
||||||
|
|
||||||
response.send(err.message);
|
response.send(err.message);
|
||||||
|
|||||||
@@ -8,7 +8,10 @@ import app from "./webserver";
|
|||||||
import { createPass } from "passkit-generator";
|
import { createPass } from "passkit-generator";
|
||||||
|
|
||||||
app.all(async function manageRequest(request, response) {
|
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 {
|
try {
|
||||||
const pass = await createPass({
|
const pass = await createPass({
|
||||||
@@ -18,10 +21,10 @@ app.all(async function manageRequest(request, response) {
|
|||||||
signerCert: "../certificates/signerCert.pem",
|
signerCert: "../certificates/signerCert.pem",
|
||||||
signerKey: {
|
signerKey: {
|
||||||
keyFile: "../certificates/signerKey.pem",
|
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
|
// 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
|
// Italian, already has an .lproj which gets included
|
||||||
pass.localize("it", {
|
pass.localize("it", {
|
||||||
"EVENT": "Evento",
|
EVENT: "Evento",
|
||||||
"LOCATION": "Dove"
|
LOCATION: "Dove",
|
||||||
});
|
});
|
||||||
|
|
||||||
// German, doesn't, so is created
|
// German, doesn't, so is created
|
||||||
pass.localize("de", {
|
pass.localize("de", {
|
||||||
"EVENT": "Ereignis",
|
EVENT: "Ereignis",
|
||||||
"LOCATION": "Ort"
|
LOCATION: "Ort",
|
||||||
});
|
});
|
||||||
|
|
||||||
// This language does not exist but is still added as .lproj folder
|
// This language does not exist but is still added as .lproj folder
|
||||||
pass.localize("zu", {});
|
pass.localize("zu", {});
|
||||||
|
|
||||||
// @ts-ignore - ignoring for logging purposes. Do not replicate
|
// @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();
|
const stream = pass.generate();
|
||||||
response.set({
|
response.set({
|
||||||
"Content-type": "application/vnd.apple.pkpass",
|
"Content-type": "application/vnd.apple.pkpass",
|
||||||
"Content-disposition": `attachment; filename=${passName}.pkpass`
|
"Content-disposition": `attachment; filename=${passName}.pkpass`,
|
||||||
});
|
});
|
||||||
|
|
||||||
stream.pipe(response);
|
stream.pipe(response);
|
||||||
} catch(err) {
|
} catch (err) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
|
|
||||||
response.set({
|
response.set({
|
||||||
|
|||||||
@@ -1,49 +1,51 @@
|
|||||||
{
|
{
|
||||||
"formatVersion": 1,
|
"formatVersion": 1,
|
||||||
"passTypeIdentifier": "pass.com.example.myapp",
|
"passTypeIdentifier": "pass.com.example.myapp",
|
||||||
"serialNumber": "nmyuxofgna",
|
"serialNumber": "nmyuxofgna",
|
||||||
"teamIdentifier": "F53WB8AE67",
|
"teamIdentifier": "F53WB8AE67",
|
||||||
"webServiceURL": "https://192.168.1.254:80/",
|
"webServiceURL": "https://192.168.1.254:80/",
|
||||||
"authenticationToken": "vxwxd7J8AlNNFPS8k0a0FfUFtq0ewzFdc",
|
"authenticationToken": "vxwxd7J8AlNNFPS8k0a0FfUFtq0ewzFdc",
|
||||||
"relevantDate": "2011-12-08T13:00-08:00",
|
"relevantDate": "2011-12-08T13:00-08:00",
|
||||||
"locations": [
|
"locations": [
|
||||||
{
|
{
|
||||||
"longitude": -122.3748889,
|
"longitude": -122.3748889,
|
||||||
"latitude": 37.6189722
|
"latitude": 37.6189722
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"longitude": -122.03118,
|
"longitude": -122.03118,
|
||||||
"latitude": 37.33182
|
"latitude": 37.33182
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"barcodes": [{
|
"barcodes": [
|
||||||
"message": "123456789",
|
{
|
||||||
"format": "PKBarcodeFormatQR",
|
"message": "123456789",
|
||||||
"messageEncoding": "iso-8859-1"
|
"format": "PKBarcodeFormatQR",
|
||||||
}],
|
"messageEncoding": "iso-8859-1"
|
||||||
"barcode": {
|
}
|
||||||
"message": "123456789",
|
],
|
||||||
"format": "PKBarcodeFormatQR",
|
"barcode": {
|
||||||
"messageEncoding": "iso-8859-1"
|
"message": "123456789",
|
||||||
},
|
"format": "PKBarcodeFormatQR",
|
||||||
"organizationName": "Apple Inc.",
|
"messageEncoding": "iso-8859-1"
|
||||||
"description": "Apple Event Ticket",
|
},
|
||||||
"foregroundColor": "rgb(255, 255, 255)",
|
"organizationName": "Apple Inc.",
|
||||||
"backgroundColor": "rgb(60, 65, 76)",
|
"description": "Apple Event Ticket",
|
||||||
"eventTicket": {
|
"foregroundColor": "rgb(255, 255, 255)",
|
||||||
"primaryFields": [
|
"backgroundColor": "rgb(60, 65, 76)",
|
||||||
{
|
"eventTicket": {
|
||||||
"key": "event",
|
"primaryFields": [
|
||||||
"label": "EVENT",
|
{
|
||||||
"value": "The Beat Goes On"
|
"key": "event",
|
||||||
}
|
"label": "EVENT",
|
||||||
],
|
"value": "The Beat Goes On"
|
||||||
"secondaryFields": [
|
}
|
||||||
{
|
],
|
||||||
"key": "loc",
|
"secondaryFields": [
|
||||||
"label": "LOCATION",
|
{
|
||||||
"value": "Moscone West"
|
"key": "loc",
|
||||||
}
|
"label": "LOCATION",
|
||||||
]
|
"value": "Moscone West"
|
||||||
}
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1116
examples/package-lock.json
generated
1116
examples/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,5 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"outDir": "build"
|
"outDir": "build"
|
||||||
},
|
},
|
||||||
"exclude": [
|
"exclude": ["node_modules"]
|
||||||
"node_modules"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export const app = express();
|
|||||||
|
|
||||||
app.use(express.json());
|
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.");
|
console.log("Webserver started.");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -17,10 +17,11 @@ app.all("/", function (request, response) {
|
|||||||
response.redirect("/gen/");
|
response.redirect("/gen/");
|
||||||
});
|
});
|
||||||
|
|
||||||
app.route("/gen")
|
app.route("/gen").all((req, res) => {
|
||||||
.all((req, res) => {
|
res.set("Content-Type", "text/html");
|
||||||
res.set("Content-Type", "text/html");
|
res.send(
|
||||||
res.send("Cannot generate a pass. Specify a modelName in the url to continue. <br/>Usage: /gen/<i>modelName</i>")
|
"Cannot generate a pass. Specify a modelName in the url to continue. <br/>Usage: /gen/<i>modelName</i>",
|
||||||
});
|
);
|
||||||
|
});
|
||||||
|
|
||||||
export default app.route("/gen/:modelName");
|
export default app.route("/gen/:modelName");
|
||||||
|
|||||||
6
package-lock.json
generated
6
package-lock.json
generated
@@ -209,6 +209,12 @@
|
|||||||
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
|
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
|
||||||
"dev": true
|
"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": {
|
"rimraf": {
|
||||||
"version": "3.0.2",
|
"version": "3.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
|
||||||
|
|||||||
@@ -41,6 +41,7 @@
|
|||||||
"@types/node-forge": "^0.9.7",
|
"@types/node-forge": "^0.9.7",
|
||||||
"@types/yazl": "^2.4.2",
|
"@types/yazl": "^2.4.2",
|
||||||
"jasmine": "^3.6.4",
|
"jasmine": "^3.6.4",
|
||||||
|
"prettier": "^2.2.1",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"typescript": "^4.1.3"
|
"typescript": "^4.1.3"
|
||||||
},
|
},
|
||||||
|
|||||||
179
spec/factory.ts
179
spec/factory.ts
@@ -6,7 +6,7 @@ import * as path from "path";
|
|||||||
describe("createPass", () => {
|
describe("createPass", () => {
|
||||||
it("should throw if first argument is not provided", async () => {
|
it("should throw if first argument is not provided", async () => {
|
||||||
await expectAsync(createPass(undefined)).toBeRejectedWithError(
|
await expectAsync(createPass(undefined)).toBeRejectedWithError(
|
||||||
formatMessage("CP_NO_OPTS")
|
formatMessage("CP_NO_OPTS"),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -27,71 +27,144 @@ describe("createPass", () => {
|
|||||||
|
|
||||||
if (certificatesPath) {
|
if (certificatesPath) {
|
||||||
it("should return a pass instance", async () => {
|
it("should return a pass instance", async () => {
|
||||||
await expectAsync(createPass({
|
await expectAsync(
|
||||||
model: path.resolve(__dirname, "../examples/models/exampleBooking.pass"),
|
createPass({
|
||||||
certificates: {
|
model: path.resolve(
|
||||||
signerCert: path.resolve(__dirname, certificatesPath, "./signerCert.pem"),
|
__dirname,
|
||||||
signerKey: {
|
"../examples/models/exampleBooking.pass",
|
||||||
keyFile: path.resolve(__dirname, certificatesPath, "./signerKey.pem"),
|
),
|
||||||
passphrase: "password1234"
|
certificates: {
|
||||||
|
signerCert: path.resolve(
|
||||||
|
__dirname,
|
||||||
|
certificatesPath,
|
||||||
|
"./signerCert.pem",
|
||||||
|
),
|
||||||
|
signerKey: {
|
||||||
|
keyFile: path.resolve(
|
||||||
|
__dirname,
|
||||||
|
certificatesPath,
|
||||||
|
"./signerKey.pem",
|
||||||
|
),
|
||||||
|
passphrase: "password1234",
|
||||||
|
},
|
||||||
|
wwdr: path.resolve(
|
||||||
|
__dirname,
|
||||||
|
certificatesPath,
|
||||||
|
"./WWDR.pem",
|
||||||
|
),
|
||||||
},
|
},
|
||||||
wwdr: path.resolve(__dirname, certificatesPath, "./WWDR.pem"),
|
}),
|
||||||
}
|
).toBeResolved();
|
||||||
})).toBeResolved();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Model validation", () => {
|
describe("Model validation", () => {
|
||||||
it("Should reject with non valid model", async () => {
|
it("Should reject with non valid model", async () => {
|
||||||
await expectAsync(createPass({
|
await expectAsync(
|
||||||
// @ts-expect-error
|
createPass({
|
||||||
model: 0,
|
// @ts-expect-error
|
||||||
certificates: {
|
model: 0,
|
||||||
signerCert: path.resolve(__dirname, certificatesPath, "./signerCert.pem"),
|
certificates: {
|
||||||
signerKey: {
|
signerCert: path.resolve(
|
||||||
keyFile: path.resolve(__dirname, certificatesPath, "./signerKey.pem"),
|
__dirname,
|
||||||
passphrase: "password1234"
|
certificatesPath,
|
||||||
|
"./signerCert.pem",
|
||||||
|
),
|
||||||
|
signerKey: {
|
||||||
|
keyFile: path.resolve(
|
||||||
|
__dirname,
|
||||||
|
certificatesPath,
|
||||||
|
"./signerKey.pem",
|
||||||
|
),
|
||||||
|
passphrase: "password1234",
|
||||||
|
},
|
||||||
|
wwdr: path.resolve(
|
||||||
|
__dirname,
|
||||||
|
certificatesPath,
|
||||||
|
"./WWDR.pem",
|
||||||
|
),
|
||||||
},
|
},
|
||||||
wwdr: path.resolve(__dirname, certificatesPath, "./WWDR.pem"),
|
}),
|
||||||
}
|
).toBeRejected();
|
||||||
})).toBeRejected();
|
|
||||||
|
|
||||||
await expectAsync(createPass({
|
await expectAsync(
|
||||||
model: undefined,
|
createPass({
|
||||||
certificates: {
|
model: undefined,
|
||||||
signerCert: path.resolve(__dirname, certificatesPath, "./signerCert.pem"),
|
certificates: {
|
||||||
signerKey: {
|
signerCert: path.resolve(
|
||||||
keyFile: path.resolve(__dirname, certificatesPath, "./signerKey.pem"),
|
__dirname,
|
||||||
passphrase: "password1234"
|
certificatesPath,
|
||||||
|
"./signerCert.pem",
|
||||||
|
),
|
||||||
|
signerKey: {
|
||||||
|
keyFile: path.resolve(
|
||||||
|
__dirname,
|
||||||
|
certificatesPath,
|
||||||
|
"./signerKey.pem",
|
||||||
|
),
|
||||||
|
passphrase: "password1234",
|
||||||
|
},
|
||||||
|
wwdr: path.resolve(
|
||||||
|
__dirname,
|
||||||
|
certificatesPath,
|
||||||
|
"./WWDR.pem",
|
||||||
|
),
|
||||||
},
|
},
|
||||||
wwdr: path.resolve(__dirname, certificatesPath, "./WWDR.pem"),
|
}),
|
||||||
}
|
).toBeRejected();
|
||||||
})).toBeRejected();
|
|
||||||
|
|
||||||
await expectAsync(createPass({
|
await expectAsync(
|
||||||
model: null,
|
createPass({
|
||||||
certificates: {
|
model: null,
|
||||||
signerCert: path.resolve(__dirname, certificatesPath, "./signerCert.pem"),
|
certificates: {
|
||||||
signerKey: {
|
signerCert: path.resolve(
|
||||||
keyFile: path.resolve(__dirname, certificatesPath, "./signerKey.pem"),
|
__dirname,
|
||||||
passphrase: "password1234"
|
certificatesPath,
|
||||||
|
"./signerCert.pem",
|
||||||
|
),
|
||||||
|
signerKey: {
|
||||||
|
keyFile: path.resolve(
|
||||||
|
__dirname,
|
||||||
|
certificatesPath,
|
||||||
|
"./signerKey.pem",
|
||||||
|
),
|
||||||
|
passphrase: "password1234",
|
||||||
|
},
|
||||||
|
wwdr: path.resolve(
|
||||||
|
__dirname,
|
||||||
|
certificatesPath,
|
||||||
|
"./WWDR.pem",
|
||||||
|
),
|
||||||
},
|
},
|
||||||
wwdr: path.resolve(__dirname, certificatesPath, "./WWDR.pem"),
|
}),
|
||||||
}
|
).toBeRejected();
|
||||||
})).toBeRejected();
|
|
||||||
|
|
||||||
await expectAsync(createPass({
|
await expectAsync(
|
||||||
model: {},
|
createPass({
|
||||||
certificates: {
|
model: {},
|
||||||
signerCert: path.resolve(__dirname, certificatesPath, "./signerCert.pem"),
|
certificates: {
|
||||||
signerKey: {
|
signerCert: path.resolve(
|
||||||
keyFile: path.resolve(__dirname, certificatesPath, "./signerKey.pem"),
|
__dirname,
|
||||||
passphrase: "password1234"
|
certificatesPath,
|
||||||
|
"./signerCert.pem",
|
||||||
|
),
|
||||||
|
signerKey: {
|
||||||
|
keyFile: path.resolve(
|
||||||
|
__dirname,
|
||||||
|
certificatesPath,
|
||||||
|
"./signerKey.pem",
|
||||||
|
),
|
||||||
|
passphrase: "password1234",
|
||||||
|
},
|
||||||
|
wwdr: path.resolve(
|
||||||
|
__dirname,
|
||||||
|
certificatesPath,
|
||||||
|
"./WWDR.pem",
|
||||||
|
),
|
||||||
},
|
},
|
||||||
wwdr: path.resolve(__dirname, certificatesPath, "./WWDR.pem"),
|
}),
|
||||||
}
|
).toBeRejected();
|
||||||
})).toBeRejected();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (err) { }
|
} catch (err) {}
|
||||||
});
|
});
|
||||||
|
|||||||
160
spec/index.ts
160
spec/index.ts
@@ -9,16 +9,25 @@ describe("Passkit-generator", function () {
|
|||||||
let pass: Pass;
|
let pass: Pass;
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
pass = await createPass({
|
pass = await createPass({
|
||||||
model: path.resolve(__dirname, "../examples/models/examplePass.pass"),
|
model: path.resolve(
|
||||||
|
__dirname,
|
||||||
|
"../examples/models/examplePass.pass",
|
||||||
|
),
|
||||||
certificates: {
|
certificates: {
|
||||||
wwdr: path.resolve(__dirname, "../certificates/WWDR.pem"),
|
wwdr: path.resolve(__dirname, "../certificates/WWDR.pem"),
|
||||||
signerCert: path.resolve(__dirname, "../certificates/signerCert.pem"),
|
signerCert: path.resolve(
|
||||||
|
__dirname,
|
||||||
|
"../certificates/signerCert.pem",
|
||||||
|
),
|
||||||
signerKey: {
|
signerKey: {
|
||||||
keyFile: path.resolve(__dirname, "../certificates/signerKey.pem"),
|
keyFile: path.resolve(
|
||||||
passphrase: "123456"
|
__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", () => {
|
it("Will apply changes if a second object argument with translations is passed", () => {
|
||||||
pass.localize("it", {
|
pass.localize("it", {
|
||||||
"Test": "Prova"
|
Test: "Prova",
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(typeof pass["l10nTranslations"]["it"]).toBe("object");
|
expect(typeof pass["l10nTranslations"]["it"]).toBe("object");
|
||||||
@@ -99,16 +108,21 @@ describe("Passkit-generator", function () {
|
|||||||
describe("locations()", () => {
|
describe("locations()", () => {
|
||||||
it("Won't apply changes if invalid location objects are passed", () => {
|
it("Won't apply changes if invalid location objects are passed", () => {
|
||||||
const props = pass.props["locations"] || [];
|
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",
|
// @ts-expect-error
|
||||||
"longitude": 0.00000000
|
ibrupofene: "no",
|
||||||
}, ...props);
|
longitude: 0.0,
|
||||||
|
},
|
||||||
|
...props,
|
||||||
|
);
|
||||||
|
|
||||||
if (oldAmountOfLocations) {
|
if (oldAmountOfLocations) {
|
||||||
expect(pass.props["locations"].length).toBe(oldAmountOfLocations);
|
expect(pass.props["locations"].length).toBe(
|
||||||
|
oldAmountOfLocations,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
expect(pass.props["locations"]).toBe(undefined);
|
expect(pass.props["locations"]).toBe(undefined);
|
||||||
}
|
}
|
||||||
@@ -116,33 +130,42 @@ describe("Passkit-generator", function () {
|
|||||||
|
|
||||||
it("Will filter out invalid location objects", () => {
|
it("Will filter out invalid location objects", () => {
|
||||||
const props = pass.props["locations"] || [];
|
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",
|
// @ts-expect-error
|
||||||
"longitude": 0.00000000
|
ibrupofene: "no",
|
||||||
}, {
|
longitude: 0.0,
|
||||||
"longitude": 4.42634523,
|
},
|
||||||
"latitude": 5.344233323352
|
{
|
||||||
}, ...(pass.props["locations"] || []));
|
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()", () => {
|
describe("Beacons()", () => {
|
||||||
it("Won't apply changes if invalid beacon objects are passed", () => {
|
it("Won't apply changes if invalid beacon objects are passed", () => {
|
||||||
const props = pass.props["beacons"] || [];
|
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",
|
// @ts-expect-error
|
||||||
"major": 55,
|
ibrupofene: "no",
|
||||||
"minor": 0,
|
major: 55,
|
||||||
"proximityUUID": "2707c5f4-deb9-48ff-b760-671bc885b6a7"
|
minor: 0,
|
||||||
}, ...props);
|
proximityUUID: "2707c5f4-deb9-48ff-b760-671bc885b6a7",
|
||||||
|
},
|
||||||
|
...props,
|
||||||
|
);
|
||||||
|
|
||||||
if (oldAmountOfBeacons) {
|
if (oldAmountOfBeacons) {
|
||||||
expect(pass.props["beacons"].length).toBe(oldAmountOfBeacons);
|
expect(pass.props["beacons"].length).toBe(oldAmountOfBeacons);
|
||||||
@@ -153,20 +176,23 @@ describe("Passkit-generator", function () {
|
|||||||
|
|
||||||
it("Will filter out invalid beacons objects", () => {
|
it("Will filter out invalid beacons objects", () => {
|
||||||
const props = pass.props["beacons"] || [];
|
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",
|
|
||||||
// @ts-expect-error
|
|
||||||
"animal": "Monkey"
|
|
||||||
}, ...props);
|
|
||||||
|
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
|
||||||
expect(pass.props["beacons"].length).toBe(oldAmountOfBeacons + 1);
|
expect(pass.props["beacons"].length).toBe(oldAmountOfBeacons + 1);
|
||||||
});
|
});
|
||||||
@@ -175,7 +201,7 @@ describe("Passkit-generator", function () {
|
|||||||
describe("barcodes()", () => {
|
describe("barcodes()", () => {
|
||||||
it("Won't apply changes if no data is passed", () => {
|
it("Won't apply changes if no data is passed", () => {
|
||||||
const props = pass.props["barcodes"] || [];
|
const props = pass.props["barcodes"] || [];
|
||||||
const oldAmountOfBarcodes = props && props.length || 0;
|
const oldAmountOfBarcodes = (props && props.length) || 0;
|
||||||
|
|
||||||
pass.barcodes();
|
pass.barcodes();
|
||||||
expect(pass.props["barcodes"].length).toBe(oldAmountOfBarcodes);
|
expect(pass.props["barcodes"].length).toBe(oldAmountOfBarcodes);
|
||||||
@@ -183,7 +209,7 @@ describe("Passkit-generator", function () {
|
|||||||
|
|
||||||
it("Will ignore boolean parameter", () => {
|
it("Will ignore boolean parameter", () => {
|
||||||
const props = pass.props["barcodes"] || [];
|
const props = pass.props["barcodes"] || [];
|
||||||
const oldAmountOfBarcodes = props && props.length || 0;
|
const oldAmountOfBarcodes = (props && props.length) || 0;
|
||||||
|
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
pass.barcode(true);
|
pass.barcode(true);
|
||||||
@@ -192,7 +218,7 @@ describe("Passkit-generator", function () {
|
|||||||
|
|
||||||
it("Will ignore numeric parameter", () => {
|
it("Will ignore numeric parameter", () => {
|
||||||
const props = pass.props["barcodes"] || [];
|
const props = pass.props["barcodes"] || [];
|
||||||
const oldAmountOfBarcodes = props && props.length || 0;
|
const oldAmountOfBarcodes = (props && props.length) || 0;
|
||||||
|
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
pass.barcodes(42);
|
pass.barcodes(42);
|
||||||
@@ -208,7 +234,7 @@ describe("Passkit-generator", function () {
|
|||||||
pass.barcodes({
|
pass.barcodes({
|
||||||
message: "28363516282",
|
message: "28363516282",
|
||||||
format: "PKBarcodeFormatPDF417",
|
format: "PKBarcodeFormatPDF417",
|
||||||
messageEncoding: "utf8"
|
messageEncoding: "utf8",
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(pass.props["barcodes"].length).toBe(1);
|
expect(pass.props["barcodes"].length).toBe(1);
|
||||||
@@ -220,12 +246,14 @@ describe("Passkit-generator", function () {
|
|||||||
format: "PKBarcodeFormatPDF417",
|
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", () => {
|
it("Will ignore objects without message property", () => {
|
||||||
const props = pass.props["barcodes"] || [];
|
const props = pass.props["barcodes"] || [];
|
||||||
const oldAmountOfBarcodes = props && props.length || 0;
|
const oldAmountOfBarcodes = (props && props.length) || 0;
|
||||||
|
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
pass.barcodes({
|
pass.barcodes({
|
||||||
@@ -237,12 +265,19 @@ describe("Passkit-generator", function () {
|
|||||||
|
|
||||||
it("Will ignore non-Barcodes schema compliant objects", () => {
|
it("Will ignore non-Barcodes schema compliant objects", () => {
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
pass.barcodes(5, 10, 15, {
|
pass.barcodes(
|
||||||
message: "28363516282",
|
5,
|
||||||
format: "PKBarcodeFormatPDF417"
|
10,
|
||||||
}, 7, 1);
|
15,
|
||||||
|
{
|
||||||
|
message: "28363516282",
|
||||||
|
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", () => {
|
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 () {
|
it("Will ignore non string or null arguments", function () {
|
||||||
const oldBarcode = pass.props["barcode"] || undefined;
|
const oldBarcode = pass.props["barcode"] || undefined;
|
||||||
|
|
||||||
pass
|
pass.barcodes("Message-22645272183")
|
||||||
.barcodes("Message-22645272183")
|
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
.barcode(55)
|
.barcode(55);
|
||||||
|
|
||||||
// unchanged
|
// unchanged
|
||||||
expect(pass.props["barcode"]).toEqual(oldBarcode);
|
expect(pass.props["barcode"]).toEqual(oldBarcode);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Will reset backward value on null", () => {
|
it("Will reset backward value on null", () => {
|
||||||
pass.barcodes("Message-22645272183")
|
pass.barcodes("Message-22645272183").barcode(
|
||||||
.barcode("PKBarcodeFormatAztec");
|
"PKBarcodeFormatAztec",
|
||||||
|
);
|
||||||
|
|
||||||
expect(pass.props["barcode"].format).toBe("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", () => {
|
it("Won't apply changes if unknown format is passed", () => {
|
||||||
const oldBarcode = pass.props["barcode"] || undefined;
|
const oldBarcode = pass.props["barcode"] || undefined;
|
||||||
|
|
||||||
pass
|
pass.barcodes("Message-22645272183")
|
||||||
.barcodes("Message-22645272183")
|
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
.barcode("PKBingoBongoFormat");
|
.barcode("PKBingoBongoFormat");
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
{
|
{
|
||||||
"spec_dir": "spec",
|
"spec_dir": "spec",
|
||||||
"spec_files": [
|
"spec_files": ["**/*.js"],
|
||||||
"**/*.js"
|
|
||||||
],
|
|
||||||
"stopSpecOnExpectationFailure": false,
|
"stopSpecOnExpectationFailure": false,
|
||||||
"random": true
|
"random": true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ describe("splitBufferBundle", () => {
|
|||||||
"de.lproj/background@2x.png": zeroBuffer,
|
"de.lproj/background@2x.png": zeroBuffer,
|
||||||
"it.lproj/thumbnail@2x.png": zeroBuffer,
|
"it.lproj/thumbnail@2x.png": zeroBuffer,
|
||||||
"thumbnail@2x.png": zeroBuffer,
|
"thumbnail@2x.png": zeroBuffer,
|
||||||
"background.png": zeroBuffer
|
"background.png": zeroBuffer,
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = splitBufferBundle(payload);
|
const result = splitBufferBundle(payload);
|
||||||
@@ -25,11 +25,11 @@ describe("splitBufferBundle", () => {
|
|||||||
},
|
},
|
||||||
"it.lproj": {
|
"it.lproj": {
|
||||||
"thumbnail@2x.png": zeroBuffer,
|
"thumbnail@2x.png": zeroBuffer,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
expect(result[1]).toEqual({
|
expect(result[1]).toEqual({
|
||||||
"thumbnail@2x.png": zeroBuffer,
|
"thumbnail@2x.png": zeroBuffer,
|
||||||
"background.png": zeroBuffer
|
"background.png": zeroBuffer,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
143
src/abstract.ts
143
src/abstract.ts
@@ -1,68 +1,75 @@
|
|||||||
import { Certificates, FinalCertificates, PartitionedBundle, OverridesSupportedOptions, FactoryOptions } from "./schema";
|
import {
|
||||||
import { getModelContents, readCertificatesFromOptions } from "./parser";
|
Certificates,
|
||||||
import formatMessage from "./messages";
|
FinalCertificates,
|
||||||
|
PartitionedBundle,
|
||||||
const abmCertificates = Symbol("certificates");
|
OverridesSupportedOptions,
|
||||||
const abmModel = Symbol("model");
|
FactoryOptions,
|
||||||
const abmOverrides = Symbol("overrides");
|
} from "./schema";
|
||||||
|
import { getModelContents, readCertificatesFromOptions } from "./parser";
|
||||||
export interface AbstractFactoryOptions extends Omit<FactoryOptions, "certificates"> {
|
import formatMessage from "./messages";
|
||||||
certificates?: Certificates;
|
|
||||||
}
|
const abmCertificates = Symbol("certificates");
|
||||||
|
const abmModel = Symbol("model");
|
||||||
interface AbstractModelOptions {
|
const abmOverrides = Symbol("overrides");
|
||||||
bundle: PartitionedBundle;
|
|
||||||
certificates: FinalCertificates;
|
export interface AbstractFactoryOptions
|
||||||
overrides?: OverridesSupportedOptions;
|
extends Omit<FactoryOptions, "certificates"> {
|
||||||
}
|
certificates?: Certificates;
|
||||||
|
}
|
||||||
/**
|
|
||||||
* Creates an abstract model to keep data
|
interface AbstractModelOptions {
|
||||||
* in memory for future passes creation
|
bundle: PartitionedBundle;
|
||||||
* @param options
|
certificates: FinalCertificates;
|
||||||
*/
|
overrides?: OverridesSupportedOptions;
|
||||||
|
}
|
||||||
export async function createAbstractModel(options: AbstractFactoryOptions) {
|
|
||||||
if (!(options && Object.keys(options).length)) {
|
/**
|
||||||
throw new Error(formatMessage("CP_NO_OPTS"));
|
* Creates an abstract model to keep data
|
||||||
}
|
* in memory for future passes creation
|
||||||
|
* @param options
|
||||||
try {
|
*/
|
||||||
const [bundle, certificates] = await Promise.all([
|
|
||||||
getModelContents(options.model),
|
export async function createAbstractModel(options: AbstractFactoryOptions) {
|
||||||
readCertificatesFromOptions(options.certificates)
|
if (!(options && Object.keys(options).length)) {
|
||||||
]);
|
throw new Error(formatMessage("CP_NO_OPTS"));
|
||||||
|
}
|
||||||
return new AbstractModel({
|
|
||||||
bundle,
|
try {
|
||||||
certificates,
|
const [bundle, certificates] = await Promise.all([
|
||||||
overrides: options.overrides
|
getModelContents(options.model),
|
||||||
});
|
readCertificatesFromOptions(options.certificates),
|
||||||
} catch (err) {
|
]);
|
||||||
throw new Error(formatMessage("CP_INIT_ERROR", "abstract model", err));
|
|
||||||
}
|
return new AbstractModel({
|
||||||
}
|
bundle,
|
||||||
|
certificates,
|
||||||
export class AbstractModel {
|
overrides: options.overrides,
|
||||||
private [abmCertificates]: FinalCertificates;
|
});
|
||||||
private [abmModel]: PartitionedBundle;
|
} catch (err) {
|
||||||
private [abmOverrides]: OverridesSupportedOptions;
|
throw new Error(formatMessage("CP_INIT_ERROR", "abstract model", err));
|
||||||
|
}
|
||||||
constructor(options: AbstractModelOptions) {
|
}
|
||||||
this[abmModel] = options.bundle;
|
|
||||||
this[abmCertificates] = options.certificates;
|
export class AbstractModel {
|
||||||
this[abmOverrides] = options.overrides
|
private [abmCertificates]: FinalCertificates;
|
||||||
}
|
private [abmModel]: PartitionedBundle;
|
||||||
|
private [abmOverrides]: OverridesSupportedOptions;
|
||||||
get certificates(): FinalCertificates {
|
|
||||||
return this[abmCertificates];
|
constructor(options: AbstractModelOptions) {
|
||||||
}
|
this[abmModel] = options.bundle;
|
||||||
|
this[abmCertificates] = options.certificates;
|
||||||
get bundle(): PartitionedBundle {
|
this[abmOverrides] = options.overrides;
|
||||||
return this[abmModel];
|
}
|
||||||
}
|
|
||||||
|
get certificates(): FinalCertificates {
|
||||||
get overrides(): OverridesSupportedOptions {
|
return this[abmCertificates];
|
||||||
return this[abmOverrides];
|
}
|
||||||
}
|
|
||||||
}
|
get bundle(): PartitionedBundle {
|
||||||
|
return this[abmModel];
|
||||||
|
}
|
||||||
|
|
||||||
|
get overrides(): OverridesSupportedOptions {
|
||||||
|
return this[abmOverrides];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
import { Pass } from "./pass";
|
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 formatMessage from "./messages";
|
||||||
import { getModelContents, readCertificatesFromOptions } from "./parser";
|
import { getModelContents, readCertificatesFromOptions } from "./parser";
|
||||||
import { splitBufferBundle } from "./utils";
|
import { splitBufferBundle } from "./utils";
|
||||||
@@ -16,9 +22,14 @@ import { AbstractModel, AbstractFactoryOptions } from "./abstract";
|
|||||||
export async function createPass(
|
export async function createPass(
|
||||||
options: FactoryOptions | InstanceType<typeof AbstractModel>,
|
options: FactoryOptions | InstanceType<typeof AbstractModel>,
|
||||||
additionalBuffers?: BundleUnit,
|
additionalBuffers?: BundleUnit,
|
||||||
abstractMissingData?: Omit<AbstractFactoryOptions, "model">
|
abstractMissingData?: Omit<AbstractFactoryOptions, "model">,
|
||||||
): Promise<Pass> {
|
): 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"));
|
throw new Error(formatMessage("CP_NO_OPTS"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,35 +38,62 @@ export async function createPass(
|
|||||||
let certificates: FinalCertificates;
|
let certificates: FinalCertificates;
|
||||||
let overrides: OverridesSupportedOptions = {
|
let overrides: OverridesSupportedOptions = {
|
||||||
...(options.overrides || {}),
|
...(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(
|
certificates = Object.assign(
|
||||||
options.certificates,
|
options.certificates,
|
||||||
await readCertificatesFromOptions(abstractMissingData.certificates)
|
await readCertificatesFromOptions(
|
||||||
|
abstractMissingData.certificates,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
certificates = options.certificates;
|
certificates = options.certificates;
|
||||||
}
|
}
|
||||||
|
|
||||||
return createPassInstance(options.bundle, certificates, overrides, additionalBuffers);
|
return createPassInstance(
|
||||||
|
options.bundle,
|
||||||
|
certificates,
|
||||||
|
overrides,
|
||||||
|
additionalBuffers,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
const [bundle, certificates] = await Promise.all([
|
const [bundle, certificates] = await Promise.all([
|
||||||
getModelContents(options.model),
|
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) {
|
} catch (err) {
|
||||||
throw new Error(formatMessage("CP_INIT_ERROR", "pass", 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) {
|
if (additionalBuffers) {
|
||||||
const [additionalL10n, additionalBundle] = splitBufferBundle(additionalBuffers);
|
const [additionalL10n, additionalBundle] = splitBufferBundle(
|
||||||
|
additionalBuffers,
|
||||||
|
);
|
||||||
Object.assign(model["l10nBundle"], additionalL10n);
|
Object.assign(model["l10nBundle"], additionalL10n);
|
||||||
Object.assign(model["bundle"], additionalBundle);
|
Object.assign(model["bundle"], additionalBundle);
|
||||||
}
|
}
|
||||||
@@ -63,6 +101,6 @@ function createPassInstance(model: PartitionedBundle, certificates: FinalCertifi
|
|||||||
return new Pass({
|
return new Pass({
|
||||||
model,
|
model,
|
||||||
certificates,
|
certificates,
|
||||||
overrides
|
overrides,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,20 +24,28 @@ export default class FieldsArray extends Array {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
push(...fieldsData: schema.Field[]): number {
|
push(...fieldsData: schema.Field[]): number {
|
||||||
const validFields = fieldsData.reduce((acc: schema.Field[], current: schema.Field) => {
|
const validFields = fieldsData.reduce(
|
||||||
if (!(typeof current === "object") || !schema.isValid(current, "field")) {
|
(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.`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this[poolSymbol].add(current.key);
|
||||||
|
acc.push(current);
|
||||||
|
}
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}
|
},
|
||||||
|
[],
|
||||||
if (this[poolSymbol].has(current.key)) {
|
);
|
||||||
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);
|
return Array.prototype.push.call(this, ...validFields);
|
||||||
}
|
}
|
||||||
@@ -58,9 +66,13 @@ export default class FieldsArray extends Array {
|
|||||||
* also uniqueKeys set
|
* 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);
|
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);
|
return Array.prototype.splice.call(this, start, deleteCount, items);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,5 +3,5 @@ import { AbstractModel as AbstractModelClass } from "./abstract";
|
|||||||
|
|
||||||
export { createPass } from "./factory";
|
export { createPass } from "./factory";
|
||||||
export { createAbstractModel } from "./abstract";
|
export { createAbstractModel } from "./abstract";
|
||||||
export type Pass = InstanceType<typeof PassClass>
|
export type Pass = InstanceType<typeof PassClass>;
|
||||||
export type AbstractModel = InstanceType<typeof AbstractModelClass>
|
export type AbstractModel = InstanceType<typeof AbstractModelClass>;
|
||||||
|
|||||||
@@ -1,31 +1,51 @@
|
|||||||
const errors = {
|
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_INIT_ERROR:
|
||||||
CP_NO_OPTS: "Cannot initialize the pass or abstract model creation: no options were passed.",
|
"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_CERTS: "Cannot initialize the pass creation: no valid certificates were passed.",
|
CP_NO_OPTS:
|
||||||
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.",
|
"Cannot initialize the pass or abstract model creation: no options were passed.",
|
||||||
REQUIR_VALID_FAILED: "The options passed to Pass constructor does not meet the requirements.\nRefer to the documentation to compile them correctly.",
|
CP_NO_CERTS:
|
||||||
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.",
|
"Cannot initialize the pass creation: no valid certificates were passed.",
|
||||||
MODEL_NOT_VALID: "A model must be provided in form of path (string) or object { 'fileName': Buffer } in order to continue.",
|
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_NOT_FOUND: "Model %s not found. Provide a valid one to continue.",
|
||||||
MODELF_FILE_NOT_FOUND: "File %s not found.",
|
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.",
|
INVALID_CERT_PATH: "Invalid certificate loaded. %s does not exist.",
|
||||||
TRSTYPE_REQUIRED: "Cannot proceed with pass creation. transitType field is required for boardingPasses.",
|
TRSTYPE_REQUIRED:
|
||||||
OVV_KEYS_BADFORMAT: "Cannot proceed with pass creation due to bad keys format in overrides.",
|
"Cannot proceed with pass creation. transitType field is required for boardingPasses.",
|
||||||
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."
|
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 = {
|
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]",
|
TRSTYPE_NOT_VALID:
|
||||||
BRC_NOT_SUPPORTED: "Format not found among barcodes. Cannot set backward compatibility.",
|
'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_FORMATTYPE_UNMATCH: "Format must be a string or null. Cannot set backward compatibility.",
|
BRC_NOT_SUPPORTED:
|
||||||
BRC_AUTC_MISSING_DATA: "Unable to autogenerate barcodes. Data is not a string.",
|
"Format not found among barcodes. Cannot set backward compatibility.",
|
||||||
BRC_BW_FORMAT_UNSUPPORTED: "This format is not supported (by Apple) for backward support. Please choose another one.",
|
BRC_FORMATTYPE_UNMATCH:
|
||||||
BRC_NO_POOL: "Cannot set barcode: no barcodes found. Please set barcodes first. Barcode is for retrocompatibility only.",
|
"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.",
|
DATE_FORMAT_UNMATCH: "%s was not set due to incorrect date format.",
|
||||||
NFC_INVALID: "Unable to set NFC properties: data not compliant with schema.",
|
NFC_INVALID:
|
||||||
PRS_INVALID: "Unable to parse Personalization.json. File is not a valid JSON. Error: %s",
|
"Unable to set NFC properties: data not compliant with schema.",
|
||||||
PRS_REMOVED: "Personalization has been removed as it requires an NFC-enabled pass to work."
|
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);
|
type AllMessages = keyof (typeof debugMessages & typeof errors);
|
||||||
|
|||||||
211
src/parser.ts
211
src/parser.ts
@@ -1,8 +1,21 @@
|
|||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import forge from "node-forge";
|
import forge from "node-forge";
|
||||||
import formatMessage from "./messages";
|
import formatMessage from "./messages";
|
||||||
import { FactoryOptions, PartitionedBundle, BundleUnit, Certificates, FinalCertificates, isValid } from "./schema";
|
import {
|
||||||
import { removeHidden, splitBufferBundle, getAllFilesWithName, hasFilesWithName, deletePersonalization } from "./utils";
|
FactoryOptions,
|
||||||
|
PartitionedBundle,
|
||||||
|
BundleUnit,
|
||||||
|
Certificates,
|
||||||
|
FinalCertificates,
|
||||||
|
isValid,
|
||||||
|
} from "./schema";
|
||||||
|
import {
|
||||||
|
removeHidden,
|
||||||
|
splitBufferBundle,
|
||||||
|
getAllFilesWithName,
|
||||||
|
hasFilesWithName,
|
||||||
|
deletePersonalization,
|
||||||
|
} from "./utils";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import debug from "debug";
|
import debug from "debug";
|
||||||
|
|
||||||
@@ -27,10 +40,9 @@ export async function getModelContents(model: FactoryOptions["model"]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const modelFiles = Object.keys(modelContents.bundle);
|
const modelFiles = Object.keys(modelContents.bundle);
|
||||||
const isModelInitialized = (
|
const isModelInitialized =
|
||||||
modelFiles.includes("pass.json") &&
|
modelFiles.includes("pass.json") &&
|
||||||
hasFilesWithName("icon", modelFiles, "startsWith")
|
hasFilesWithName("icon", modelFiles, "startsWith");
|
||||||
);
|
|
||||||
|
|
||||||
if (!isModelInitialized) {
|
if (!isModelInitialized) {
|
||||||
throw new Error(formatMessage("MODEL_UNINITIALIZED", "parse result"));
|
throw new Error(formatMessage("MODEL_UNINITIALIZED", "parse result"));
|
||||||
@@ -46,19 +58,34 @@ export async function getModelContents(model: FactoryOptions["model"]) {
|
|||||||
return modelContents;
|
return modelContents;
|
||||||
}
|
}
|
||||||
|
|
||||||
const logoFullNames = getAllFilesWithName("personalizationLogo", modelFiles, "startsWith");
|
const logoFullNames = getAllFilesWithName(
|
||||||
if (!(logoFullNames.length && modelContents.bundle[personalizationJsonFile].length)) {
|
"personalizationLogo",
|
||||||
|
modelFiles,
|
||||||
|
"startsWith",
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
!(
|
||||||
|
logoFullNames.length &&
|
||||||
|
modelContents.bundle[personalizationJsonFile].length
|
||||||
|
)
|
||||||
|
) {
|
||||||
deletePersonalization(modelContents.bundle, logoFullNames);
|
deletePersonalization(modelContents.bundle, logoFullNames);
|
||||||
return modelContents;
|
return modelContents;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const parsedPersonalization = JSON.parse(modelContents.bundle[personalizationJsonFile].toString("utf8"));
|
const parsedPersonalization = JSON.parse(
|
||||||
const isPersonalizationValid = isValid(parsedPersonalization, "personalizationDict");
|
modelContents.bundle[personalizationJsonFile].toString("utf8"),
|
||||||
|
);
|
||||||
|
const isPersonalizationValid = isValid(
|
||||||
|
parsedPersonalization,
|
||||||
|
"personalizationDict",
|
||||||
|
);
|
||||||
|
|
||||||
if (!isPersonalizationValid) {
|
if (!isPersonalizationValid) {
|
||||||
[...logoFullNames, personalizationJsonFile]
|
[...logoFullNames, personalizationJsonFile].forEach(
|
||||||
.forEach(file => delete modelContents.bundle[file]);
|
(file) => delete modelContents.bundle[file],
|
||||||
|
);
|
||||||
|
|
||||||
return modelContents;
|
return modelContents;
|
||||||
}
|
}
|
||||||
@@ -76,55 +103,70 @@ export async function getModelContents(model: FactoryOptions["model"]) {
|
|||||||
* @param model
|
* @param model
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export async function getModelFolderContents(model: string): Promise<PartitionedBundle> {
|
export async function getModelFolderContents(
|
||||||
|
model: string,
|
||||||
|
): Promise<PartitionedBundle> {
|
||||||
try {
|
try {
|
||||||
const modelPath = `${model}${!path.extname(model) && ".pass" || ""}`;
|
const modelPath = `${model}${(!path.extname(model) && ".pass") || ""}`;
|
||||||
const modelFilesList = await readDir(modelPath);
|
const modelFilesList = await readDir(modelPath);
|
||||||
|
|
||||||
// No dot-starting files, manifest and signature
|
// No dot-starting files, manifest and signature
|
||||||
const filteredFiles = removeHidden(modelFilesList)
|
const filteredFiles = removeHidden(modelFilesList).filter(
|
||||||
.filter(f => !/(manifest|signature)/i.test(f) && /.+$/.test(path.parse(f).ext));
|
(f) =>
|
||||||
|
!/(manifest|signature)/i.test(f) &&
|
||||||
const isModelInitialized = (
|
/.+$/.test(path.parse(f).ext),
|
||||||
filteredFiles.length &&
|
|
||||||
hasFilesWithName("icon", filteredFiles, "startsWith")
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const isModelInitialized =
|
||||||
|
filteredFiles.length &&
|
||||||
|
hasFilesWithName("icon", filteredFiles, "startsWith");
|
||||||
|
|
||||||
// Icon is required to proceed
|
// Icon is required to proceed
|
||||||
if (!isModelInitialized) {
|
if (!isModelInitialized) {
|
||||||
throw new Error(formatMessage(
|
throw new Error(
|
||||||
"MODEL_UNINITIALIZED",
|
formatMessage("MODEL_UNINITIALIZED", path.parse(model).name),
|
||||||
path.parse(model).name
|
);
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Splitting files from localization folders
|
// Splitting files from localization folders
|
||||||
const rawBundleFiles = filteredFiles.filter(entry => !entry.includes(".lproj"));
|
const rawBundleFiles = filteredFiles.filter(
|
||||||
const l10nFolders = filteredFiles.filter(entry => entry.includes(".lproj"));
|
(entry) => !entry.includes(".lproj"),
|
||||||
|
);
|
||||||
const rawBundleBuffers = await Promise.all(
|
const l10nFolders = filteredFiles.filter((entry) =>
|
||||||
rawBundleFiles.map(file => readFile(path.resolve(modelPath, file)))
|
entry.includes(".lproj"),
|
||||||
);
|
);
|
||||||
|
|
||||||
const bundle: BundleUnit = Object.assign({},
|
const rawBundleBuffers = await Promise.all(
|
||||||
...rawBundleFiles.map((fileName, index) => ({ [fileName]: rawBundleBuffers[index] }))
|
rawBundleFiles.map((file) =>
|
||||||
|
readFile(path.resolve(modelPath, file)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const bundle: BundleUnit = Object.assign(
|
||||||
|
{},
|
||||||
|
...rawBundleFiles.map((fileName, index) => ({
|
||||||
|
[fileName]: rawBundleBuffers[index],
|
||||||
|
})),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Reading concurrently localizations folder
|
// Reading concurrently localizations folder
|
||||||
// and their files and their buffers
|
// and their files and their buffers
|
||||||
const L10N_FilesListByFolder: Array<BundleUnit> = await Promise.all(
|
const L10N_FilesListByFolder: Array<BundleUnit> = await Promise.all(
|
||||||
l10nFolders.map(async folderPath => {
|
l10nFolders.map(async (folderPath) => {
|
||||||
// Reading current folder
|
// Reading current folder
|
||||||
const currentLangPath = path.join(modelPath, folderPath);
|
const currentLangPath = path.join(modelPath, folderPath);
|
||||||
|
|
||||||
const files = await readDir(currentLangPath);
|
const files = await readDir(currentLangPath);
|
||||||
// Transforming files path to a model-relative path
|
// Transforming files path to a model-relative path
|
||||||
const validFiles = removeHidden(files)
|
const validFiles = removeHidden(files).map((file) =>
|
||||||
.map(file => path.join(currentLangPath, file));
|
path.join(currentLangPath, file),
|
||||||
|
);
|
||||||
|
|
||||||
// Getting all the buffers from file paths
|
// Getting all the buffers from file paths
|
||||||
const buffers = await Promise.all(
|
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
|
// Assigning each file path to its buffer
|
||||||
@@ -140,34 +182,37 @@ export async function getModelFolderContents(model: string): Promise<Partitioned
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...acc,
|
...acc,
|
||||||
[fileName]: buffers[index]
|
[fileName]: buffers[index],
|
||||||
};
|
};
|
||||||
}, {});
|
}, {});
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const l10nBundle: PartitionedBundle["l10nBundle"] = Object.assign(
|
const l10nBundle: PartitionedBundle["l10nBundle"] = Object.assign(
|
||||||
{},
|
{},
|
||||||
...L10N_FilesListByFolder
|
...L10N_FilesListByFolder.map((folder, index) => ({
|
||||||
.map((folder, index) => ({ [l10nFolders[index]]: folder }))
|
[l10nFolders[index]]: folder,
|
||||||
|
})),
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
bundle,
|
bundle,
|
||||||
l10nBundle
|
l10nBundle,
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err?.code === "ENOENT") {
|
if (err?.code === "ENOENT") {
|
||||||
if (err.syscall === "open") {
|
if (err.syscall === "open") {
|
||||||
// file opening failed
|
// 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") {
|
} else if (err.syscall === "scandir") {
|
||||||
// directory reading failed
|
// directory reading failed
|
||||||
const pathContents = (err.path as string).split(/(\/|\\\?)/);
|
const pathContents = (err.path as string).split(/(\/|\\\?)/);
|
||||||
throw new Error(formatMessage(
|
throw new Error(
|
||||||
"MODELF_FILE_NOT_FOUND",
|
formatMessage(
|
||||||
pathContents[pathContents.length - 1]
|
"MODELF_FILE_NOT_FOUND",
|
||||||
))
|
pathContents[pathContents.length - 1],
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,27 +227,28 @@ export async function getModelFolderContents(model: string): Promise<Partitioned
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export function getModelBufferContents(model: BundleUnit): PartitionedBundle {
|
export function getModelBufferContents(model: BundleUnit): PartitionedBundle {
|
||||||
const rawBundle = removeHidden(Object.keys(model)).reduce<BundleUnit>((acc, current) => {
|
const rawBundle = removeHidden(Object.keys(model)).reduce<BundleUnit>(
|
||||||
// Checking if current file is one of the autogenerated ones or if its
|
(acc, current) => {
|
||||||
// content is not available
|
// Checking if current file is one of the autogenerated ones or if its
|
||||||
|
// content is not available
|
||||||
|
|
||||||
if (/(manifest|signature)/.test(current) || !model[current]) {
|
if (/(manifest|signature)/.test(current) || !model[current]) {
|
||||||
return acc;
|
return acc;
|
||||||
}
|
}
|
||||||
|
|
||||||
return { ...acc, [current]: model[current] };
|
return { ...acc, [current]: model[current] };
|
||||||
}, {});
|
},
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
||||||
const bundleKeys = Object.keys(rawBundle);
|
const bundleKeys = Object.keys(rawBundle);
|
||||||
|
|
||||||
const isModelInitialized = (
|
const isModelInitialized =
|
||||||
bundleKeys.length &&
|
bundleKeys.length && hasFilesWithName("icon", bundleKeys, "startsWith");
|
||||||
hasFilesWithName("icon", bundleKeys, "startsWith")
|
|
||||||
);
|
|
||||||
|
|
||||||
// Icon is required to proceed
|
// Icon is required to proceed
|
||||||
if (!isModelInitialized) {
|
if (!isModelInitialized) {
|
||||||
throw new Error(formatMessage("MODEL_UNINITIALIZED", "Buffers"))
|
throw new Error(formatMessage("MODEL_UNINITIALIZED", "Buffers"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// separing localization folders from bundle files
|
// separing localization folders from bundle files
|
||||||
@@ -210,7 +256,7 @@ export function getModelBufferContents(model: BundleUnit): PartitionedBundle {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
bundle,
|
bundle,
|
||||||
l10nBundle
|
l10nBundle,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,8 +270,16 @@ type flatCertificates = Omit<Certificates, "signerKey"> & {
|
|||||||
signerKey: string;
|
signerKey: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function readCertificatesFromOptions(options: Certificates): Promise<FinalCertificates> {
|
export async function readCertificatesFromOptions(
|
||||||
if (!(options && Object.keys(options).length && isValid(options, "certificatesSchema"))) {
|
options: Certificates,
|
||||||
|
): Promise<FinalCertificates> {
|
||||||
|
if (
|
||||||
|
!(
|
||||||
|
options &&
|
||||||
|
Object.keys(options).length &&
|
||||||
|
isValid(options, "certificatesSchema")
|
||||||
|
)
|
||||||
|
) {
|
||||||
throw new Error(formatMessage("CP_NO_CERTS"));
|
throw new Error(formatMessage("CP_NO_CERTS"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,30 +293,31 @@ export async function readCertificatesFromOptions(options: Certificates): Promis
|
|||||||
|
|
||||||
// if the signerKey is an object, we want to get
|
// if the signerKey is an object, we want to get
|
||||||
// all the real contents and don't care of passphrase
|
// 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
|
// We read the contents
|
||||||
const rawContentsPromises = Object.keys(flattenedDocs)
|
const rawContentsPromises = Object.keys(flattenedDocs).map((key) => {
|
||||||
.map(key => {
|
const content = flattenedDocs[key];
|
||||||
const content = flattenedDocs[key];
|
|
||||||
|
|
||||||
if (!!path.parse(content).ext) {
|
if (!!path.parse(content).ext) {
|
||||||
// The content is a path to the document
|
// The content is a path to the document
|
||||||
return readFile(path.resolve(content), { encoding: "utf8" });
|
return readFile(path.resolve(content), { encoding: "utf8" });
|
||||||
} else {
|
} else {
|
||||||
// Content is the real document content
|
// Content is the real document content
|
||||||
return Promise.resolve(content);
|
return Promise.resolve(content);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const parsedContents = await Promise.all(rawContentsPromises);
|
const parsedContents = await Promise.all(rawContentsPromises);
|
||||||
const pemParsedContents = parsedContents.map((file, index) => {
|
const pemParsedContents = parsedContents.map((file, index) => {
|
||||||
const certName = Object.keys(options)[index];
|
const certName = Object.keys(options)[index];
|
||||||
const passphrase = (
|
const passphrase =
|
||||||
typeof options.signerKey === "object" &&
|
(typeof options.signerKey === "object" &&
|
||||||
options.signerKey?.passphrase
|
options.signerKey?.passphrase) ||
|
||||||
) || undefined;
|
undefined;
|
||||||
|
|
||||||
const pem = parsePEM(certName, file, passphrase);
|
const pem = parsePEM(certName, file, passphrase);
|
||||||
|
|
||||||
@@ -279,7 +334,9 @@ export async function readCertificatesFromOptions(options: Certificates): Promis
|
|||||||
throw err;
|
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),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
328
src/pass.ts
328
src/pass.ts
@@ -7,7 +7,13 @@ import { ZipFile } from "yazl";
|
|||||||
import * as schema from "./schema";
|
import * as schema from "./schema";
|
||||||
import formatMessage from "./messages";
|
import formatMessage from "./messages";
|
||||||
import FieldsArray from "./fieldsArray";
|
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 barcodeDebug = debug("passkit:barcode");
|
||||||
const genericDebug = debug("passkit:generic");
|
const genericDebug = debug("passkit:generic");
|
||||||
@@ -20,7 +26,7 @@ const propsSchemaMap = new Map<string, schema.Schema>([
|
|||||||
["barcode", "barcode"],
|
["barcode", "barcode"],
|
||||||
["beacons", "beaconsDict"],
|
["beacons", "beaconsDict"],
|
||||||
["locations", "locationsDict"],
|
["locations", "locationsDict"],
|
||||||
["nfc", "nfcDict"]
|
["nfc", "nfcDict"],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export class Pass {
|
export class Pass {
|
||||||
@@ -42,7 +48,9 @@ export class Pass {
|
|||||||
|
|
||||||
private Certificates: schema.FinalCertificates;
|
private Certificates: schema.FinalCertificates;
|
||||||
private [transitType]: string = "";
|
private [transitType]: string = "";
|
||||||
private l10nTranslations: { [languageCode: string]: { [placeholder: string]: string } } = {};
|
private l10nTranslations: {
|
||||||
|
[languageCode: string]: { [placeholder: string]: string };
|
||||||
|
} = {};
|
||||||
|
|
||||||
constructor(options: schema.PassInstance) {
|
constructor(options: schema.PassInstance) {
|
||||||
if (!schema.isValid(options, "instance")) {
|
if (!schema.isValid(options, "instance")) {
|
||||||
@@ -54,77 +62,101 @@ export class Pass {
|
|||||||
this.bundle = { ...options.model.bundle };
|
this.bundle = { ...options.model.bundle };
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.passCore = JSON.parse(this.bundle["pass.json"].toString("utf8"));
|
this.passCore = JSON.parse(
|
||||||
|
this.bundle["pass.json"].toString("utf8"),
|
||||||
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new Error(formatMessage("PASSFILE_VALIDATION_FAILED"));
|
throw new Error(formatMessage("PASSFILE_VALIDATION_FAILED"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parsing the options and extracting only the valid ones.
|
// 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) {
|
if (validOverrides === null) {
|
||||||
throw new Error(formatMessage("OVV_KEYS_BADFORMAT"))
|
throw new Error(formatMessage("OVV_KEYS_BADFORMAT"));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.type = Object.keys(this.passCore)
|
this.type = Object.keys(this.passCore).find((key) =>
|
||||||
.find(key => /(boardingPass|eventTicket|coupon|generic|storeCard)/.test(key)) as keyof schema.ValidPassType;
|
/(boardingPass|eventTicket|coupon|generic|storeCard)/.test(key),
|
||||||
|
) as keyof schema.ValidPassType;
|
||||||
|
|
||||||
if (!this.type) {
|
if (!this.type) {
|
||||||
throw new Error(formatMessage("NO_PASS_TYPE"));
|
throw new Error(formatMessage("NO_PASS_TYPE"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parsing and validating pass.json keys
|
// Parsing and validating pass.json keys
|
||||||
const passCoreKeys = Object.keys(this.passCore) as (keyof schema.ValidPass)[];
|
const passCoreKeys = Object.keys(
|
||||||
const validatedPassKeys = passCoreKeys.reduce<schema.ValidPass>((acc, current) => {
|
this.passCore,
|
||||||
if (this.type === current) {
|
) as (keyof schema.ValidPass)[];
|
||||||
// We want to exclude type keys (eventTicket,
|
const validatedPassKeys = passCoreKeys.reduce<schema.ValidPass>(
|
||||||
// boardingPass, ecc.) and their content
|
(acc, current) => {
|
||||||
return acc;
|
if (this.type === current) {
|
||||||
}
|
// We want to exclude type keys (eventTicket,
|
||||||
|
// boardingPass, ecc.) and their content
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
if (!propsSchemaMap.has(current)) {
|
if (!propsSchemaMap.has(current)) {
|
||||||
// If the property is unknown (we don't care if
|
// If the property is unknown (we don't care if
|
||||||
// it is valid or not for Wallet), we return
|
// it is valid or not for Wallet), we return
|
||||||
// directly the content
|
// directly the content
|
||||||
return { ...acc, [current]: this.passCore[current] };
|
return { ...acc, [current]: this.passCore[current] };
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentSchema = propsSchemaMap.get(current)!;
|
const currentSchema = propsSchemaMap.get(current)!;
|
||||||
|
|
||||||
if (Array.isArray(this.passCore[current])) {
|
if (Array.isArray(this.passCore[current])) {
|
||||||
const valid = getValidInArray<schema.ArrayPassSchema>(
|
const valid = getValidInArray<schema.ArrayPassSchema>(
|
||||||
currentSchema,
|
currentSchema,
|
||||||
this.passCore[current] as schema.ArrayPassSchema[]
|
this.passCore[current] as schema.ArrayPassSchema[],
|
||||||
);
|
);
|
||||||
return { ...acc, [current]: valid };
|
return { ...acc, [current]: valid };
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
...acc,
|
...acc,
|
||||||
[current]: schema.isValid(
|
[current]:
|
||||||
this.passCore[current],
|
(schema.isValid(
|
||||||
currentSchema
|
this.passCore[current],
|
||||||
) && this.passCore[current] || undefined
|
currentSchema,
|
||||||
};
|
) &&
|
||||||
}
|
this.passCore[current]) ||
|
||||||
}, {});
|
undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
||||||
this[passProps] = {
|
this[passProps] = {
|
||||||
...(validatedPassKeys || {}),
|
...(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
|
// We might want to generate a boarding pass without setting manually
|
||||||
// in the code the transit type but right in the model;
|
// in the code the transit type but right in the model;
|
||||||
this[transitType] = this.passCore[this.type]["transitType"];
|
this[transitType] = this.passCore[this.type]["transitType"];
|
||||||
}
|
}
|
||||||
|
|
||||||
this._fields = ["primaryFields", "secondaryFields", "auxiliaryFields", "backFields", "headerFields"];
|
this._fields = [
|
||||||
this._fields.forEach(fieldName => {
|
"primaryFields",
|
||||||
|
"secondaryFields",
|
||||||
|
"auxiliaryFields",
|
||||||
|
"backFields",
|
||||||
|
"headerFields",
|
||||||
|
];
|
||||||
|
this._fields.forEach((fieldName) => {
|
||||||
this[fieldName] = new FieldsArray(
|
this[fieldName] = new FieldsArray(
|
||||||
this.fieldsKeys,
|
this.fieldsKeys,
|
||||||
...(this.passCore[this.type][fieldName] || [])
|
...(this.passCore[this.type][fieldName] || []).filter((field) =>
|
||||||
.filter(field => schema.isValid(field, "field"))
|
schema.isValid(field, "field"),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -146,13 +178,19 @@ export class Pass {
|
|||||||
*/
|
*/
|
||||||
const currentBundleFiles = Object.keys(this.bundle);
|
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"));
|
genericDebug(formatMessage("PRS_REMOVED"));
|
||||||
deletePersonalization(this.bundle, getAllFilesWithName(
|
deletePersonalization(
|
||||||
"personalizationLogo",
|
this.bundle,
|
||||||
currentBundleFiles,
|
getAllFilesWithName(
|
||||||
"startsWith"
|
"personalizationLogo",
|
||||||
));
|
currentBundleFiles,
|
||||||
|
"startsWith",
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const finalBundle = { ...this.bundle } as schema.BundleUnit;
|
const finalBundle = { ...this.bundle } as schema.BundleUnit;
|
||||||
@@ -161,7 +199,7 @@ export class Pass {
|
|||||||
* Iterating through languages and generating pass.string file
|
* 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 strings = generateStringFile(this.l10nTranslations[lang]);
|
||||||
const langInBundles = `${lang}.lproj`;
|
const langInBundles = `${lang}.lproj`;
|
||||||
|
|
||||||
@@ -176,13 +214,21 @@ export class Pass {
|
|||||||
this.l10nBundles[langInBundles] = {};
|
this.l10nBundles[langInBundles] = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
this.l10nBundles[langInBundles]["pass.strings"] = Buffer.concat([
|
this.l10nBundles[langInBundles][
|
||||||
this.l10nBundles[langInBundles]["pass.strings"] || Buffer.alloc(0),
|
"pass.strings"
|
||||||
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,33 +240,48 @@ export class Pass {
|
|||||||
* composition.
|
* composition.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Object.assign(finalBundle, ...Object.keys(this.l10nBundles[langInBundles])
|
Object.assign(
|
||||||
.map(fileName => {
|
finalBundle,
|
||||||
const fullPath = path.join(langInBundles, fileName).replace(/\\/, "/");
|
...Object.keys(this.l10nBundles[langInBundles]).map(
|
||||||
return { [fullPath]: this.l10nBundles[langInBundles][fileName] };
|
(fileName) => {
|
||||||
})
|
const fullPath = path
|
||||||
|
.join(langInBundles, fileName)
|
||||||
|
.replace(/\\/, "/");
|
||||||
|
return {
|
||||||
|
[fullPath]: this.l10nBundles[langInBundles][
|
||||||
|
fileName
|
||||||
|
],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Parsing the buffers, pushing them into the archive
|
* Parsing the buffers, pushing them into the archive
|
||||||
* and returning the compiled manifest
|
* and returning the compiled manifest
|
||||||
*/
|
*/
|
||||||
const archive = new ZipFile();
|
const archive = new ZipFile();
|
||||||
const manifest = Object.keys(finalBundle).reduce<schema.Manifest>((acc, current) => {
|
const manifest = Object.keys(finalBundle).reduce<schema.Manifest>(
|
||||||
let hashFlow = forge.md.sha1.create();
|
(acc, current) => {
|
||||||
|
let hashFlow = forge.md.sha1.create();
|
||||||
|
|
||||||
hashFlow.update(finalBundle[current].toString("binary"));
|
hashFlow.update(finalBundle[current].toString("binary"));
|
||||||
archive.addBuffer(finalBundle[current], current);
|
archive.addBuffer(finalBundle[current], current);
|
||||||
acc[current] = hashFlow.digest().toHex();
|
acc[current] = hashFlow.digest().toHex();
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
},
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
||||||
const signatureBuffer = this._sign(manifest);
|
const signatureBuffer = this._sign(manifest);
|
||||||
|
|
||||||
archive.addBuffer(signatureBuffer, "signature");
|
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();
|
const passStream = new Stream.PassThrough();
|
||||||
|
|
||||||
archive.outputStream.pipe(passStream);
|
archive.outputStream.pipe(passStream);
|
||||||
@@ -242,8 +303,15 @@ export class Pass {
|
|||||||
* @see https://apple.co/2KOv0OW - Passes support localization
|
* @see https://apple.co/2KOv0OW - Passes support localization
|
||||||
*/
|
*/
|
||||||
|
|
||||||
localize(lang: string, translations?: { [placeholder: string]: string }): this {
|
localize(
|
||||||
if (lang && typeof lang === "string" && (typeof translations === "object" || translations === undefined)) {
|
lang: string,
|
||||||
|
translations?: { [placeholder: string]: string },
|
||||||
|
): this {
|
||||||
|
if (
|
||||||
|
lang &&
|
||||||
|
typeof lang === "string" &&
|
||||||
|
(typeof translations === "object" || translations === undefined)
|
||||||
|
) {
|
||||||
this.l10nTranslations[lang] = translations || {};
|
this.l10nTranslations[lang] = translations || {};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,7 +360,7 @@ export class Pass {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
beacons(resetFlag: null): this;
|
beacons(resetFlag: null): this;
|
||||||
beacons(...data: schema.Beacon[]): this
|
beacons(...data: schema.Beacon[]): this;
|
||||||
beacons(...data: (schema.Beacon | null)[]): this {
|
beacons(...data: (schema.Beacon | null)[]): this {
|
||||||
if (data[0] === null) {
|
if (data[0] === null) {
|
||||||
delete this[passProps]["beacons"];
|
delete this[passProps]["beacons"];
|
||||||
@@ -322,7 +390,10 @@ export class Pass {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
const valid = processRelevancySet("locations", data as schema.Location[]);
|
const valid = processRelevancySet(
|
||||||
|
"locations",
|
||||||
|
data as schema.Location[],
|
||||||
|
);
|
||||||
|
|
||||||
if (valid.length) {
|
if (valid.length) {
|
||||||
this[passProps]["locations"] = valid;
|
this[passProps]["locations"] = valid;
|
||||||
@@ -390,19 +461,28 @@ export class Pass {
|
|||||||
* Validation assign default value to missing parameters (if any).
|
* Validation assign default value to missing parameters (if any).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const validBarcodes = data.reduce<schema.Barcode[]>((acc, current) => {
|
const validBarcodes = data.reduce<schema.Barcode[]>(
|
||||||
if (!(current && current instanceof Object)) {
|
(acc, current) => {
|
||||||
return acc;
|
if (!(current && current instanceof Object)) {
|
||||||
}
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
const validated = schema.getValidated(current, "barcode");
|
const validated = schema.getValidated(current, "barcode");
|
||||||
|
|
||||||
if (!(validated && validated instanceof Object && Object.keys(validated).length)) {
|
if (
|
||||||
return acc;
|
!(
|
||||||
}
|
validated &&
|
||||||
|
validated instanceof Object &&
|
||||||
|
Object.keys(validated).length
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
return [...acc, validated] as schema.Barcode[];
|
return [...acc, validated] as schema.Barcode[];
|
||||||
}, []);
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
if (validBarcodes.length) {
|
if (validBarcodes.length) {
|
||||||
this[passProps]["barcodes"] = validBarcodes;
|
this[passProps]["barcodes"] = validBarcodes;
|
||||||
@@ -446,7 +526,9 @@ export class Pass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Checking which object among barcodes has the same format of the specified one.
|
// 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) {
|
if (index === -1) {
|
||||||
barcodeDebug(formatMessage("BRC_NOT_SUPPORTED"));
|
barcodeDebug(formatMessage("BRC_NOT_SUPPORTED"));
|
||||||
@@ -472,7 +554,14 @@ export class Pass {
|
|||||||
return this;
|
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"));
|
genericDebug(formatMessage("NFC_INVALID"));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@@ -505,7 +594,10 @@ export class Pass {
|
|||||||
private _sign(manifest: schema.Manifest): Buffer {
|
private _sign(manifest: schema.Manifest): Buffer {
|
||||||
const signature = forge.pkcs7.createSignedData();
|
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.wwdr);
|
||||||
signature.addCertificate(this.Certificates.signerCert);
|
signature.addCertificate(this.Certificates.signerCert);
|
||||||
@@ -523,14 +615,18 @@ export class Pass {
|
|||||||
key: this.Certificates.signerKey,
|
key: this.Certificates.signerKey,
|
||||||
certificate: this.Certificates.signerCert,
|
certificate: this.Certificates.signerCert,
|
||||||
digestAlgorithm: forge.pki.oids.sha1,
|
digestAlgorithm: forge.pki.oids.sha1,
|
||||||
authenticatedAttributes: [{
|
authenticatedAttributes: [
|
||||||
type: forge.pki.oids.contentType,
|
{
|
||||||
value: forge.pki.oids.data
|
type: forge.pki.oids.contentType,
|
||||||
}, {
|
value: forge.pki.oids.data,
|
||||||
type: forge.pki.oids.messageDigest,
|
},
|
||||||
}, {
|
{
|
||||||
type: forge.pki.oids.signingTime,
|
type: forge.pki.oids.messageDigest,
|
||||||
}]
|
},
|
||||||
|
{
|
||||||
|
type: forge.pki.oids.signingTime,
|
||||||
|
},
|
||||||
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -554,7 +650,10 @@ export class Pass {
|
|||||||
* of beautiful things. ¯\_(ツ)_/¯
|
* 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 {
|
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) {
|
if (Object.keys(this[passProps]).length) {
|
||||||
/*
|
/*
|
||||||
@@ -575,14 +676,22 @@ export class Pass {
|
|||||||
* and then delete it from the passFile.
|
* and then delete it from the passFile.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const passColors = ["backgroundColor", "foregroundColor", "labelColor"] as Array<keyof schema.PassColors>;
|
const passColors = [
|
||||||
passColors.filter(v => this[passProps][v] && !isValidRGB(this[passProps][v]))
|
"backgroundColor",
|
||||||
.forEach(v => delete this[passProps][v]);
|
"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]);
|
Object.assign(passFile, this[passProps]);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._fields.forEach(field => {
|
this._fields.forEach((field) => {
|
||||||
passFile[this.type][field] = this[field];
|
passFile[this.type][field] = this[field];
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -627,8 +736,14 @@ function barcodesFromUncompleteData(message: string): schema.Barcode[] {
|
|||||||
"PKBarcodeFormatQR",
|
"PKBarcodeFormatQR",
|
||||||
"PKBarcodeFormatPDF417",
|
"PKBarcodeFormatPDF417",
|
||||||
"PKBarcodeFormatAztec",
|
"PKBarcodeFormatAztec",
|
||||||
"PKBarcodeFormatCode128"
|
"PKBarcodeFormatCode128",
|
||||||
].map(format => schema.getValidated({ format, message }, "barcode") as schema.Barcode);
|
].map(
|
||||||
|
(format) =>
|
||||||
|
schema.getValidated(
|
||||||
|
{ format, message },
|
||||||
|
"barcode",
|
||||||
|
) as schema.Barcode,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function processRelevancySet<T>(key: string, data: T[]): T[] {
|
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[] {
|
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 {
|
function processDate(key: string, date: Date): string | null {
|
||||||
|
|||||||
254
src/schema.ts
254
src/schema.ts
@@ -10,10 +10,12 @@ export interface Manifest {
|
|||||||
export interface Certificates {
|
export interface Certificates {
|
||||||
wwdr?: string;
|
wwdr?: string;
|
||||||
signerCert?: string;
|
signerCert?: string;
|
||||||
signerKey?: {
|
signerKey?:
|
||||||
keyFile: string;
|
| {
|
||||||
passphrase?: string;
|
keyFile: string;
|
||||||
} | string;
|
passphrase?: string;
|
||||||
|
}
|
||||||
|
| string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FactoryOptions {
|
export interface FactoryOptions {
|
||||||
@@ -29,7 +31,7 @@ export interface BundleUnit {
|
|||||||
export interface PartitionedBundle {
|
export interface PartitionedBundle {
|
||||||
bundle: BundleUnit;
|
bundle: BundleUnit;
|
||||||
l10nBundle: {
|
l10nBundle: {
|
||||||
[key: string]: BundleUnit
|
[key: string]: BundleUnit;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,17 +51,24 @@ export interface PassInstance {
|
|||||||
// * JOI Schemas + Related Interfaces * //
|
// * JOI Schemas + Related Interfaces * //
|
||||||
// ************************************ //
|
// ************************************ //
|
||||||
|
|
||||||
const certificatesSchema = Joi.object().keys({
|
const certificatesSchema = Joi.object()
|
||||||
wwdr: Joi.alternatives(Joi.binary(), Joi.string()).required(),
|
.keys({
|
||||||
signerCert: Joi.alternatives(Joi.binary(), Joi.string()).required(),
|
wwdr: Joi.alternatives(Joi.binary(), Joi.string()).required(),
|
||||||
signerKey: Joi.alternatives().try(
|
signerCert: Joi.alternatives(Joi.binary(), Joi.string()).required(),
|
||||||
Joi.object().keys({
|
signerKey: Joi.alternatives()
|
||||||
keyFile: Joi.alternatives(Joi.binary(), Joi.string()).required(),
|
.try(
|
||||||
passphrase: Joi.string().required(),
|
Joi.object().keys({
|
||||||
}),
|
keyFile: Joi.alternatives(
|
||||||
Joi.alternatives(Joi.binary(), Joi.string())
|
Joi.binary(),
|
||||||
).required()
|
Joi.string(),
|
||||||
}).required();
|
).required(),
|
||||||
|
passphrase: Joi.string().required(),
|
||||||
|
}),
|
||||||
|
Joi.alternatives(Joi.binary(), Joi.string()),
|
||||||
|
)
|
||||||
|
.required(),
|
||||||
|
})
|
||||||
|
.required();
|
||||||
|
|
||||||
const instance = Joi.object().keys({
|
const instance = Joi.object().keys({
|
||||||
model: Joi.alternatives(Joi.object(), Joi.string()).required(),
|
model: Joi.alternatives(Joi.object(), Joi.string()).required(),
|
||||||
@@ -88,28 +97,31 @@ export interface OverridesSupportedOptions {
|
|||||||
maxDistance?: number;
|
maxDistance?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const supportedOptions = Joi.object().keys({
|
const supportedOptions = Joi.object()
|
||||||
serialNumber: Joi.string(),
|
.keys({
|
||||||
description: Joi.string(),
|
serialNumber: Joi.string(),
|
||||||
organizationName: Joi.string(),
|
description: Joi.string(),
|
||||||
passTypeIdentifier: Joi.string(),
|
organizationName: Joi.string(),
|
||||||
teamIdentifier: Joi.string(),
|
passTypeIdentifier: Joi.string(),
|
||||||
appLaunchURL: Joi.string(),
|
teamIdentifier: Joi.string(),
|
||||||
associatedStoreIdentifiers: Joi.array().items(Joi.number()),
|
appLaunchURL: Joi.string(),
|
||||||
userInfo: Joi.alternatives(Joi.object().unknown(), Joi.array()),
|
associatedStoreIdentifiers: Joi.array().items(Joi.number()),
|
||||||
// parsing url as set of words and nums followed by dots, optional port and any possible path after
|
userInfo: Joi.alternatives(Joi.object().unknown(), Joi.array()),
|
||||||
webServiceURL: Joi.string().regex(/https?:\/\/(?:[a-z0-9]+\.?)+(?::\d{2,})?(?:\/[\S]+)*/),
|
// parsing url as set of words and nums followed by dots, optional port and any possible path after
|
||||||
authenticationToken: Joi.string().min(16),
|
webServiceURL: Joi.string().regex(
|
||||||
sharingProhibited: Joi.boolean(),
|
/https?:\/\/(?:[a-z0-9]+\.?)+(?::\d{2,})?(?:\/[\S]+)*/,
|
||||||
backgroundColor: Joi.string().min(10).max(16),
|
),
|
||||||
foregroundColor: Joi.string().min(10).max(16),
|
authenticationToken: Joi.string().min(16),
|
||||||
labelColor: Joi.string().min(10).max(16),
|
sharingProhibited: Joi.boolean(),
|
||||||
groupingIdentifier: Joi.string(),
|
backgroundColor: Joi.string().min(10).max(16),
|
||||||
suppressStripShine: Joi.boolean(),
|
foregroundColor: Joi.string().min(10).max(16),
|
||||||
logoText: Joi.string(),
|
labelColor: Joi.string().min(10).max(16),
|
||||||
maxDistance: Joi.number().positive(),
|
groupingIdentifier: Joi.string(),
|
||||||
}).with("webServiceURL", "authenticationToken");
|
suppressStripShine: Joi.boolean(),
|
||||||
|
logoText: Joi.string(),
|
||||||
|
maxDistance: Joi.number().positive(),
|
||||||
|
})
|
||||||
|
.with("webServiceURL", "authenticationToken");
|
||||||
|
|
||||||
/* For a correct usage of semantics, please refer to https://apple.co/2I66Phk */
|
/* For a correct usage of semantics, please refer to https://apple.co/2I66Phk */
|
||||||
|
|
||||||
@@ -130,7 +142,7 @@ interface PersonNameComponent {
|
|||||||
|
|
||||||
const personNameComponents = Joi.object().keys({
|
const personNameComponents = Joi.object().keys({
|
||||||
givenName: Joi.string().required(),
|
givenName: Joi.string().required(),
|
||||||
familyName: Joi.string().required()
|
familyName: Joi.string().required(),
|
||||||
});
|
});
|
||||||
|
|
||||||
interface Seat {
|
interface Seat {
|
||||||
@@ -148,12 +160,12 @@ const seat = Joi.object().keys({
|
|||||||
seatNumber: Joi.string(),
|
seatNumber: Joi.string(),
|
||||||
seatIdentifier: Joi.string(),
|
seatIdentifier: Joi.string(),
|
||||||
seatType: Joi.string(),
|
seatType: Joi.string(),
|
||||||
seatDescription: Joi.string()
|
seatDescription: Joi.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const location = Joi.object().keys({
|
const location = Joi.object().keys({
|
||||||
latitude: Joi.number().required(),
|
latitude: Joi.number().required(),
|
||||||
longitude: Joi.number().required()
|
longitude: Joi.number().required(),
|
||||||
});
|
});
|
||||||
|
|
||||||
interface Semantics {
|
interface Semantics {
|
||||||
@@ -201,7 +213,15 @@ interface Semantics {
|
|||||||
venueEntrance?: string;
|
venueEntrance?: string;
|
||||||
venuePhoneNumber?: string;
|
venuePhoneNumber?: string;
|
||||||
venueRoom?: string;
|
venueRoom?: string;
|
||||||
eventType?: "PKEventTypeGeneric" | "PKEventTypeLivePerformance" | "PKEventTypeMovie" | "PKEventTypeSports" | "PKEventTypeConference" | "PKEventTypeConvention" | "PKEventTypeWorkshop" | "PKEventTypeSocialGathering";
|
eventType?:
|
||||||
|
| "PKEventTypeGeneric"
|
||||||
|
| "PKEventTypeLivePerformance"
|
||||||
|
| "PKEventTypeMovie"
|
||||||
|
| "PKEventTypeSports"
|
||||||
|
| "PKEventTypeConference"
|
||||||
|
| "PKEventTypeConvention"
|
||||||
|
| "PKEventTypeWorkshop"
|
||||||
|
| "PKEventTypeSocialGathering";
|
||||||
eventStartDate?: string;
|
eventStartDate?: string;
|
||||||
eventEndDate?: string;
|
eventEndDate?: string;
|
||||||
artistIDs?: string;
|
artistIDs?: string;
|
||||||
@@ -270,7 +290,9 @@ const semantics = Joi.object().keys({
|
|||||||
venueEntrance: Joi.string(),
|
venueEntrance: Joi.string(),
|
||||||
venuePhoneNumber: Joi.string(),
|
venuePhoneNumber: Joi.string(),
|
||||||
venueRoom: 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(),
|
eventStartDate: Joi.string(),
|
||||||
eventEndDate: Joi.string(),
|
eventEndDate: Joi.string(),
|
||||||
artistIDs: Joi.string(),
|
artistIDs: Joi.string(),
|
||||||
@@ -287,7 +309,7 @@ const semantics = Joi.object().keys({
|
|||||||
awayTeamAbbreviation: Joi.string(),
|
awayTeamAbbreviation: Joi.string(),
|
||||||
sportName: Joi.string(),
|
sportName: Joi.string(),
|
||||||
// Store Card Passes
|
// Store Card Passes
|
||||||
balance: currencyAmount
|
balance: currencyAmount,
|
||||||
});
|
});
|
||||||
|
|
||||||
export interface ValidPassType {
|
export interface ValidPassType {
|
||||||
@@ -310,11 +332,16 @@ interface PassInterfacesProps {
|
|||||||
voided?: boolean;
|
voided?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
type AllPassProps = PassInterfacesProps & ValidPassType & OverridesSupportedOptions;
|
type AllPassProps = PassInterfacesProps &
|
||||||
|
ValidPassType &
|
||||||
|
OverridesSupportedOptions;
|
||||||
export type ValidPass = {
|
export type ValidPass = {
|
||||||
[K in keyof AllPassProps]: AllPassProps[K];
|
[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 {
|
export interface Barcode {
|
||||||
altText?: string;
|
altText?: string;
|
||||||
@@ -323,13 +350,22 @@ export interface Barcode {
|
|||||||
message: string;
|
message: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type BarcodeFormat = "PKBarcodeFormatQR" | "PKBarcodeFormatPDF417" | "PKBarcodeFormatAztec" | "PKBarcodeFormatCode128";
|
export type BarcodeFormat =
|
||||||
|
| "PKBarcodeFormatQR"
|
||||||
|
| "PKBarcodeFormatPDF417"
|
||||||
|
| "PKBarcodeFormatAztec"
|
||||||
|
| "PKBarcodeFormatCode128";
|
||||||
|
|
||||||
const barcode = Joi.object().keys({
|
const barcode = Joi.object().keys({
|
||||||
altText: Joi.string(),
|
altText: Joi.string(),
|
||||||
messageEncoding: Joi.string().default("iso-8859-1"),
|
messageEncoding: Joi.string().default("iso-8859-1"),
|
||||||
format: Joi.string().required().regex(/(PKBarcodeFormatQR|PKBarcodeFormatPDF417|PKBarcodeFormatAztec|PKBarcodeFormatCode128)/, "barcodeType"),
|
format: Joi.string()
|
||||||
message: Joi.string().required()
|
.required()
|
||||||
|
.regex(
|
||||||
|
/(PKBarcodeFormatQR|PKBarcodeFormatPDF417|PKBarcodeFormatAztec|PKBarcodeFormatCode128)/,
|
||||||
|
"barcodeType",
|
||||||
|
),
|
||||||
|
message: Joi.string().required(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export interface Field {
|
export interface Field {
|
||||||
@@ -350,30 +386,53 @@ export interface Field {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const field = Joi.object().keys({
|
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(),
|
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(""),
|
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(),
|
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,
|
semantics,
|
||||||
// date fields formatters, all optionals
|
// 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(),
|
ignoreTimeZone: Joi.boolean(),
|
||||||
isRelative: 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
|
// number fields formatters, all optionals
|
||||||
currencyCode: Joi.string()
|
currencyCode: Joi.string().when("value", {
|
||||||
.when("value", {
|
is: Joi.number(),
|
||||||
is: Joi.number(),
|
otherwise: Joi.string().forbidden(),
|
||||||
otherwise: Joi.string().forbidden()
|
}),
|
||||||
}),
|
|
||||||
numberStyle: Joi.string()
|
numberStyle: Joi.string()
|
||||||
.regex(/(PKNumberStyleDecimal|PKNumberStylePercent|PKNumberStyleScientific|PKNumberStyleSpellOut)/)
|
.regex(
|
||||||
|
/(PKNumberStyleDecimal|PKNumberStylePercent|PKNumberStyleScientific|PKNumberStyleSpellOut)/,
|
||||||
|
)
|
||||||
.when("value", {
|
.when("value", {
|
||||||
is: Joi.number(),
|
is: Joi.number(),
|
||||||
otherwise: Joi.string().forbidden()
|
otherwise: Joi.string().forbidden(),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -385,10 +444,14 @@ export interface Beacon {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const beaconsDict = Joi.object().keys({
|
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),
|
minor: Joi.number().integer().min(0).max(65535),
|
||||||
proximityUUID: Joi.string().required(),
|
proximityUUID: Joi.string().required(),
|
||||||
relevantText: Joi.string()
|
relevantText: Joi.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export interface Location {
|
export interface Location {
|
||||||
@@ -402,7 +465,7 @@ const locationsDict = Joi.object().keys({
|
|||||||
altitude: Joi.number(),
|
altitude: Joi.number(),
|
||||||
latitude: Joi.number().required(),
|
latitude: Joi.number().required(),
|
||||||
longitude: Joi.number().required(),
|
longitude: Joi.number().required(),
|
||||||
relevantText: Joi.string()
|
relevantText: Joi.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export interface PassFields {
|
export interface PassFields {
|
||||||
@@ -414,18 +477,29 @@ export interface PassFields {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const passDict = Joi.object().keys({
|
const passDict = Joi.object().keys({
|
||||||
auxiliaryFields: Joi.array().items(Joi.object().keys({
|
auxiliaryFields: Joi.array().items(
|
||||||
row: Joi.number().max(1).min(0)
|
Joi.object()
|
||||||
}).concat(field)),
|
.keys({
|
||||||
|
row: Joi.number().max(1).min(0),
|
||||||
|
})
|
||||||
|
.concat(field),
|
||||||
|
),
|
||||||
backFields: Joi.array().items(field),
|
backFields: Joi.array().items(field),
|
||||||
headerFields: Joi.array().items(field),
|
headerFields: Joi.array().items(field),
|
||||||
primaryFields: 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 {
|
export interface NFC {
|
||||||
message: string;
|
message: string;
|
||||||
@@ -434,7 +508,7 @@ export interface NFC {
|
|||||||
|
|
||||||
const nfcDict = Joi.object().keys({
|
const nfcDict = Joi.object().keys({
|
||||||
message: Joi.string().required().max(64),
|
message: Joi.string().required().max(64),
|
||||||
encryptionPublicKey: Joi.string()
|
encryptionPublicKey: Joi.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
// ************************************* //
|
// ************************************* //
|
||||||
@@ -447,11 +521,20 @@ export interface Personalization {
|
|||||||
termsAndConditions?: string;
|
termsAndConditions?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
type PRSField = "PKPassPersonalizationFieldName" | "PKPassPersonalizationFieldPostalCode" | "PKPassPersonalizationFieldEmailAddress" | "PKPassPersonalizationFieldPhoneNumber";
|
type PRSField =
|
||||||
|
| "PKPassPersonalizationFieldName"
|
||||||
|
| "PKPassPersonalizationFieldPostalCode"
|
||||||
|
| "PKPassPersonalizationFieldEmailAddress"
|
||||||
|
| "PKPassPersonalizationFieldPhoneNumber";
|
||||||
|
|
||||||
const personalizationDict = Joi.object().keys({
|
const personalizationDict = Joi.object().keys({
|
||||||
requiredPersonalizationFields: Joi.array()
|
requiredPersonalizationFields: Joi.array()
|
||||||
.items("PKPassPersonalizationFieldName", "PKPassPersonalizationFieldPostalCode", "PKPassPersonalizationFieldEmailAddress", "PKPassPersonalizationFieldPhoneNumber")
|
.items(
|
||||||
|
"PKPassPersonalizationFieldName",
|
||||||
|
"PKPassPersonalizationFieldPostalCode",
|
||||||
|
"PKPassPersonalizationFieldEmailAddress",
|
||||||
|
"PKPassPersonalizationFieldPhoneNumber",
|
||||||
|
)
|
||||||
.required(),
|
.required(),
|
||||||
description: Joi.string().required(),
|
description: Joi.string().required(),
|
||||||
termsAndConditions: Joi.string(),
|
termsAndConditions: Joi.string(),
|
||||||
@@ -470,7 +553,7 @@ const schemas = {
|
|||||||
transitType,
|
transitType,
|
||||||
nfcDict,
|
nfcDict,
|
||||||
supportedOptions,
|
supportedOptions,
|
||||||
personalizationDict
|
personalizationDict,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Schema = keyof typeof schemas;
|
export type Schema = keyof typeof schemas;
|
||||||
@@ -491,14 +574,18 @@ export function isValid(opts: any, schemaName: Schema): boolean {
|
|||||||
const resolvedSchema = resolveSchemaName(schemaName);
|
const resolvedSchema = resolveSchemaName(schemaName);
|
||||||
|
|
||||||
if (!resolvedSchema) {
|
if (!resolvedSchema) {
|
||||||
schemaDebug(`validation failed due to missing or mispelled schema name`);
|
schemaDebug(
|
||||||
|
`validation failed due to missing or mispelled schema name`,
|
||||||
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const validation = resolvedSchema.validate(opts);
|
const validation = resolvedSchema.validate(opts);
|
||||||
|
|
||||||
if (validation.error) {
|
if (validation.error) {
|
||||||
schemaDebug(`validation failed due to error: ${validation.error.message}`);
|
schemaDebug(
|
||||||
|
`validation failed due to error: ${validation.error.message}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return !validation.error;
|
return !validation.error;
|
||||||
@@ -511,18 +598,25 @@ export function isValid(opts: any, schemaName: Schema): boolean {
|
|||||||
* @returns {object} the filtered value or empty object
|
* @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);
|
const resolvedSchema = resolveSchemaName(schemaName);
|
||||||
|
|
||||||
if (!resolvedSchema) {
|
if (!resolvedSchema) {
|
||||||
schemaDebug(`validation failed due to missing or mispelled schema name`);
|
schemaDebug(
|
||||||
|
`validation failed due to missing or mispelled schema name`,
|
||||||
|
);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const validation = resolvedSchema.validate(opts, { stripUnknown: true });
|
const validation = resolvedSchema.validate(opts, { stripUnknown: true });
|
||||||
|
|
||||||
if (validation.error) {
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
89
src/utils.ts
89
src/utils.ts
@@ -16,13 +16,15 @@ export function isValidRGB(value?: string): boolean {
|
|||||||
return false;
|
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) {
|
if (!rgb) {
|
||||||
return false;
|
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> {
|
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:
|
// Pass.strings format is the following one for each row:
|
||||||
// "key" = "value";
|
// "key" = "value";
|
||||||
|
|
||||||
const strings = Object.keys(lang)
|
const strings = Object.keys(lang).map(
|
||||||
.map(key => `"${key}" = "${lang[key].replace(/"/g, '\"')}";`);
|
(key) => `"${key}" = "${lang[key].replace(/"/g, '"')}";`,
|
||||||
|
);
|
||||||
|
|
||||||
return Buffer.from(strings.join(EOL), "utf8");
|
return Buffer.from(strings.join(EOL), "utf8");
|
||||||
}
|
}
|
||||||
@@ -89,47 +92,73 @@ export function generateStringFile(lang: { [index: string]: string }): Buffer {
|
|||||||
* @param origin
|
* @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 = [{}, {}];
|
const initialValue: PartitionedBundleElements = [{}, {}];
|
||||||
|
|
||||||
if (!origin) {
|
if (!origin) {
|
||||||
return initialValue;
|
return initialValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Object.entries(origin).reduce<PartitionedBundleElements>(([l10n, bundle], [key, buffer]) => {
|
return Object.entries(origin).reduce<PartitionedBundleElements>(
|
||||||
if (!key.includes(".lproj")) {
|
([l10n, bundle], [key, buffer]) => {
|
||||||
return [
|
if (!key.includes(".lproj")) {
|
||||||
l10n,
|
return [
|
||||||
{
|
l10n,
|
||||||
...bundle,
|
{
|
||||||
[key]: buffer
|
...bundle,
|
||||||
}
|
[key]: buffer,
|
||||||
];
|
},
|
||||||
}
|
];
|
||||||
|
}
|
||||||
|
|
||||||
const pathComponents = key.split(sep);
|
const pathComponents = key.split(sep);
|
||||||
const lang = pathComponents[0];
|
const lang = pathComponents[0];
|
||||||
const file = pathComponents.slice(1).join("/");
|
const file = pathComponents.slice(1).join("/");
|
||||||
|
|
||||||
(l10n[lang] || (l10n[lang] = {}))[file] = buffer;
|
(l10n[lang] || (l10n[lang] = {}))[file] = buffer;
|
||||||
|
|
||||||
return [l10n, bundle];
|
return [l10n, bundle];
|
||||||
}, initialValue);
|
},
|
||||||
|
initialValue,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
type StringSearchMode = "includes" | "startsWith" | "endsWith";
|
type StringSearchMode = "includes" | "startsWith" | "endsWith";
|
||||||
|
|
||||||
export function getAllFilesWithName(name: string, source: string[], mode: StringSearchMode = "includes", forceLowerCase: boolean = false): string[] {
|
export function getAllFilesWithName(
|
||||||
return source.filter(file => (forceLowerCase && file.toLowerCase() || file)[mode](name));
|
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 {
|
export function hasFilesWithName(
|
||||||
return source.some(file => (forceLowerCase && file.toLowerCase() || file)[mode](name));
|
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 {
|
export function deletePersonalization(
|
||||||
[...logosNames, "personalization.json"]
|
source: BundleUnit,
|
||||||
.forEach(file => delete source[file]);
|
logosNames: string[] = [],
|
||||||
|
): void {
|
||||||
|
[...logosNames, "personalization.json"].forEach(
|
||||||
|
(file) => delete source[file],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user