mirror of
https://github.com/marcogll/passkit-generator.git
synced 2026-03-15 20:25:26 +00:00
Added check for pass type (borderPass, eventTicket, shopping, etc.);
Added _validateType method for the previous implementation; Added error support in async.parallel usage; Renamed modelPath in computedModelPath; Removed unused variables;
This commit is contained in:
86
index.js
86
index.js
@@ -4,22 +4,8 @@ const forge = require("node-forge");
|
|||||||
const archiver = require("archiver");
|
const archiver = require("archiver");
|
||||||
const async = require("async");
|
const async = require("async");
|
||||||
const stream = require("stream");
|
const stream = require("stream");
|
||||||
const Joi = require("joi");
|
|
||||||
const settingSchema = require("./schema.js");
|
const settingSchema = require("./schema.js");
|
||||||
|
|
||||||
const supportedTypesOfPass = /(boardingPass|eventTicket|coupon|generic|storeCard)/i;
|
|
||||||
const Certificates = {
|
|
||||||
status: false
|
|
||||||
};
|
|
||||||
|
|
||||||
const Configuration = {
|
|
||||||
passModelsDir: null,
|
|
||||||
output: {
|
|
||||||
shouldWrite: false,
|
|
||||||
dir: null,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Apply a filter to arg0 to remove hidden files names (starting with dot)
|
Apply a filter to arg0 to remove hidden files names (starting with dot)
|
||||||
@function removeHiddenFiles
|
@function removeHiddenFiles
|
||||||
@@ -31,10 +17,6 @@ function removeHiddenFiles(from) {
|
|||||||
return from.filter(e => e.charAt(0) !== ".");
|
return from.filter(e => e.charAt(0) !== ".");
|
||||||
}
|
}
|
||||||
|
|
||||||
function capitalizeFirst(str) {
|
|
||||||
return str[0].toUpperCase()+str.slice(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
class Pass {
|
class Pass {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
this.overrides = options.overrides || {};
|
this.overrides = options.overrides || {};
|
||||||
@@ -42,7 +24,8 @@ class Pass {
|
|||||||
this.handlers = {};
|
this.handlers = {};
|
||||||
this.modelDirectory = null;
|
this.modelDirectory = null;
|
||||||
this._parseSettings(options)
|
this._parseSettings(options)
|
||||||
.then(() => console.log("WAT IS", this))
|
;// .then(() => console.log("WAT IS", this));
|
||||||
|
this.passTypes = /^(boardingPass|eventTicket|coupon|generic|storeCard)$/;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -64,9 +47,9 @@ class Pass {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let modelPath = path.resolve(this.modelDirectory, `${this.modelName}.pass`);
|
let computedModelPath = path.resolve(this.modelDirectory, `${this.modelName}.pass`);
|
||||||
|
|
||||||
fs.readdir(modelPath, (err, files) => {
|
fs.readdir(computedModelPath, (err, files) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
return reject({
|
return reject({
|
||||||
status: false,
|
status: false,
|
||||||
@@ -104,11 +87,11 @@ class Pass {
|
|||||||
let folderList = files.filter(f => f.includes(".lproj"));
|
let folderList = files.filter(f => f.includes(".lproj"));
|
||||||
|
|
||||||
// I may have (and I rathered) used async.concat to achieve this but it returns a list of filenames ordered by folder.
|
// I may have (and I rathered) used async.concat to achieve this but it returns a list of filenames ordered by folder.
|
||||||
// The problem rise when I have to understand which is the first file of a folder which is not the first one.
|
// The problem rises when I have to understand which is the first file of a folder which is not the first one.
|
||||||
// By doing this way, I get an Array containing an array of filenames for each folder.
|
// By doing this way, I get an Array containing an array of filenames for each folder.
|
||||||
|
|
||||||
let folderExtractors = folderList.map(f => function(callback) {
|
let folderExtractors = folderList.map(f => function(callback) {
|
||||||
let l10nPath = path.join(modelPath, f);
|
let l10nPath = path.join(computedModelPath, f);
|
||||||
|
|
||||||
fs.readdir(l10nPath, function(err, list) {
|
fs.readdir(l10nPath, function(err, list) {
|
||||||
if (err) {
|
if (err) {
|
||||||
@@ -129,20 +112,30 @@ class Pass {
|
|||||||
// Using async.parallel since the final part must be executed only when both are completed.
|
// Using async.parallel since the final part must be executed only when both are completed.
|
||||||
// Otherwise would had to put everything in editPassStructure's Promise .then().
|
// Otherwise would had to put everything in editPassStructure's Promise .then().
|
||||||
async.parallel([
|
async.parallel([
|
||||||
(passCallback) => {
|
passCallback => {
|
||||||
fs.readFile(path.resolve(this.modelDirectory, `${this.modelName}.pass`, "pass.json"), {}, (err, passStructBuffer) => {
|
fs.readFile(path.resolve(this.modelDirectory, `${this.modelName}.pass`, "pass.json"), {}, (err, passStructBuffer) => {
|
||||||
|
if (!this._validateType(passStructBuffer)) {
|
||||||
|
return passCallback({
|
||||||
|
status: false,
|
||||||
|
error: {
|
||||||
|
message: `Unable to validate pass type or pass file is not a valid buffer. Refer to https://apple.co/2Nvshvn to use a valid type.`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this._patch(this._filterOptions(this.overrides), passStructBuffer)
|
this._patch(this._filterOptions(this.overrides), passStructBuffer)
|
||||||
.then(function _afterJSONParse(passFileBuffer) {
|
.then(function _afterJSONParse(passFileBuffer) {
|
||||||
manifest["pass.json"] = forge.md.sha1.create().update(passFileBuffer.toString("binary")).digest().toHex();
|
manifest["pass.json"] = forge.md.sha1.create().update(passFileBuffer.toString("binary")).digest().toHex();
|
||||||
archive.append(passFileBuffer, { name: "pass.json" });
|
archive.append(passFileBuffer, { name: "pass.json" });
|
||||||
|
|
||||||
|
// no errors happened
|
||||||
return passCallback(null);
|
return passCallback(null);
|
||||||
})
|
})
|
||||||
.catch(function(err) {
|
.catch(function(err) {
|
||||||
return reject({
|
return passCallback({
|
||||||
status: false,
|
status: false,
|
||||||
error: {
|
error: {
|
||||||
message: `pass.json Buffer is not a valid buffer. Unable to continue.\n${err}`,
|
message: `Unable to read pass.json as buffer @ ${computedModelPath}. Unable to continue.\n${err}`,
|
||||||
ecode: 418
|
ecode: 418
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -150,7 +143,7 @@ class Pass {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
(bundleCallback) => {
|
bundleCallback => {
|
||||||
async.each(list, (file, callback) => {
|
async.each(list, (file, callback) => {
|
||||||
if (/(manifest|signature|pass)/ig.test(file)) {
|
if (/(manifest|signature|pass)/ig.test(file)) {
|
||||||
// skipping files
|
// skipping files
|
||||||
@@ -187,7 +180,11 @@ class Pass {
|
|||||||
return bundleCallback(null);
|
return bundleCallback(null);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
], () => {
|
], (error) => {
|
||||||
|
if (error) {
|
||||||
|
return reject(error);
|
||||||
|
}
|
||||||
|
|
||||||
archive.append(JSON.stringify(manifest), { name: "manifest.json" });
|
archive.append(JSON.stringify(manifest), { name: "manifest.json" });
|
||||||
|
|
||||||
let signatureBuffer = this._sign(manifest);
|
let signatureBuffer = this._sign(manifest);
|
||||||
@@ -207,6 +204,18 @@ class Pass {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_validateType(passBuffer) {
|
||||||
|
try {
|
||||||
|
let passFile = JSON.parse(passBuffer.toString("utf8"));
|
||||||
|
|
||||||
|
return Object.keys(passFile).some(key => this.passTypes.test(key));
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Generates the PKCS #7 cryptografic signature for the manifest file.
|
Generates the PKCS #7 cryptografic signature for the manifest file.
|
||||||
|
|
||||||
@@ -219,7 +228,7 @@ class Pass {
|
|||||||
let signature = forge.pkcs7.createSignedData();
|
let signature = forge.pkcs7.createSignedData();
|
||||||
|
|
||||||
if (typeof manifest === "object") {
|
if (typeof manifest === "object") {
|
||||||
signature.content = forge.util.createBuffer(JSON.stringify(manifest), "utf8")
|
signature.content = forge.util.createBuffer(JSON.stringify(manifest), "utf8");
|
||||||
} else if (typeof manifest === "string") {
|
} else if (typeof manifest === "string") {
|
||||||
signature.content = manifest;
|
signature.content = manifest;
|
||||||
} else {
|
} else {
|
||||||
@@ -265,7 +274,7 @@ class Pass {
|
|||||||
// Converting the JSON Structure into a DER (which is a subset of BER), ASN.1 valid structure
|
// Converting the JSON Structure into a DER (which is a subset of BER), ASN.1 valid structure
|
||||||
// Returning the buffer of the signature
|
// Returning the buffer of the signature
|
||||||
|
|
||||||
return Buffer.from(forge.asn1.toDer(signature.toAsn1()).getBytes(), 'binary');
|
return Buffer.from(forge.asn1.toDer(signature.toAsn1()).getBytes(), "binary");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -286,7 +295,7 @@ class Pass {
|
|||||||
try {
|
try {
|
||||||
let passFile = JSON.parse(passBuffer.toString("utf8"));
|
let passFile = JSON.parse(passBuffer.toString("utf8"));
|
||||||
|
|
||||||
for (prop in options) {
|
for (let prop in options) {
|
||||||
passFile[prop] = options[prop];
|
passFile[prop] = options[prop];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -342,7 +351,7 @@ class Pass {
|
|||||||
let options = {};
|
let options = {};
|
||||||
|
|
||||||
Object.keys(supportedOptions).forEach(function(key) {
|
Object.keys(supportedOptions).forEach(function(key) {
|
||||||
if (!!query[key]) {
|
if (query[key]) {
|
||||||
if (!supportedOptions[key] || typeof supportedOptions[key] !== "function" || typeof supportedOptions[key] === "function" && supportedOptions[key](query[key])) {
|
if (!supportedOptions[key] || typeof supportedOptions[key] !== "function" || typeof supportedOptions[key] === "function" && supportedOptions[key](query[key])) {
|
||||||
options[key] = query[key];
|
options[key] = query[key];
|
||||||
}
|
}
|
||||||
@@ -372,16 +381,21 @@ class Pass {
|
|||||||
// };
|
// };
|
||||||
|
|
||||||
if (!settingSchema.validate(options)) {
|
if (!settingSchema.validate(options)) {
|
||||||
throw new Error("The options passed to Pass constructor does not meet the requirements. Refer to the documentation to compile them correctly.")
|
throw new Error("The options passed to Pass constructor does not meet the requirements. Refer to the documentation to compile them correctly.");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.modelDirectory = path.resolve(__dirname, options.modelDir);
|
this.modelDirectory = path.resolve(__dirname, options.modelDir);
|
||||||
this.Certificates.dir = options.certificates.dir;
|
this.Certificates.dir = options.certificates.dir;
|
||||||
this.modelName = options.modelName;
|
this.modelName = options.modelName;
|
||||||
|
|
||||||
let certPaths = Object.keys(options.certificates).filter(v => v !== "dir").map((val) => {
|
let certPaths = Object.keys(options.certificates)
|
||||||
return path.resolve(this.Certificates.dir, typeof options.certificates[val] !== "object" ? options.certificates[val] : options.certificates[val]["keyFile"])
|
.filter(v => v !== "dir")
|
||||||
});
|
.map((val) =>
|
||||||
|
path.resolve(
|
||||||
|
this.Certificates.dir,
|
||||||
|
typeof options.certificates[val] !== "object" ? options.certificates[val] : options.certificates[val]["keyFile"]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
async.parallel([
|
async.parallel([
|
||||||
(function __certificatesParser(callback) {
|
(function __certificatesParser(callback) {
|
||||||
|
|||||||
Reference in New Issue
Block a user