mirror of
https://github.com/marcogll/passkit-generator.git
synced 2026-03-15 16:25:21 +00:00
Added functions getModelBufferContents, getModelFolderContents and getModelContents;
This commit is contained in:
413
src/factory.ts
413
src/factory.ts
@@ -1,144 +1,269 @@
|
||||
import { Pass } from "./pass";
|
||||
import { Certificates, isValid } from "./schema";
|
||||
|
||||
import { promisify } from "util";
|
||||
import { readFile as _readFile, readdir as _readdir } from "fs";
|
||||
import * as path from "path";
|
||||
import forge from "node-forge";
|
||||
import formatMessage from "./messages";
|
||||
|
||||
const readDir = promisify(_readdir);
|
||||
const readFile = promisify(_readFile);
|
||||
|
||||
interface FactoryOptions {
|
||||
model: string | { [key: string]: Buffer },
|
||||
certificates: Certificates;
|
||||
overrides?: Object;
|
||||
}
|
||||
|
||||
async function createPass(options: FactoryOptions) {
|
||||
if (!(options && Object.keys(options).length)) {
|
||||
throw new Error("Unable to create Pass: no options were passed");
|
||||
}
|
||||
|
||||
// Voglio leggere i certificati
|
||||
// Voglio leggere il model (se non è un oggetto)
|
||||
|
||||
/* Model checks */
|
||||
|
||||
if (!options.model) {
|
||||
throw new Error("Unable to create Pass: no model passed");
|
||||
}
|
||||
|
||||
if (typeof options.model !== "string" && typeof options.model !== "object") {
|
||||
throw new Error("Unable to create Pass: unsupported type");
|
||||
}
|
||||
|
||||
if (typeof options.model === "object" && !Object.keys(options.model).length) {
|
||||
throw new Error("Unable to create Pass: object model has no content");
|
||||
}
|
||||
|
||||
/* Certificates checks */
|
||||
|
||||
const { certificates } = await Promise.all([
|
||||
readCertificatesFromOptions(options.certificates)
|
||||
]);
|
||||
|
||||
// Controllo se il model è un oggetto o una stringa
|
||||
// Se è un oggetto passo avanti
|
||||
// Se è una stringa controllo se è un path. Se è un path
|
||||
// faccio readdir
|
||||
// altrimenti throw
|
||||
|
||||
// Creare una funzione che possa controllare ed estrarre i certificati
|
||||
// Creare una funzione che possa controllare ed estrarre i file
|
||||
// Entrambe devono ritornare Promise, così faccio await Promise.all
|
||||
|
||||
return new Pass();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads certificate contents, if the passed content is a path,
|
||||
* and parses them as a PEM.
|
||||
* @param options
|
||||
*/
|
||||
|
||||
interface FinalCertificates {
|
||||
wwdr: forge.pki.Certificate;
|
||||
signerCert: forge.pki.Certificate;
|
||||
signerKey: forge.pki.PrivateKey;
|
||||
}
|
||||
|
||||
async function readCertificatesFromOptions(options: Certificates): Promise<FinalCertificates> {
|
||||
if (!isValid(options, "certificatesSchema")) {
|
||||
throw new Error("Unable to create Pass: certificates schema validation failed.");
|
||||
}
|
||||
|
||||
// if the signerKey is an object, we want to get
|
||||
// all the real contents and don't care of passphrase
|
||||
const flattenedDocs = Object.assign({}, options, {
|
||||
signerKey: (
|
||||
typeof options.signerKey === "string"
|
||||
? options.signerKey
|
||||
: options.signerKey.keyFile
|
||||
)
|
||||
});
|
||||
|
||||
// We read the contents
|
||||
const rawContentsPromises = Object.keys(flattenedDocs)
|
||||
.map(content => {
|
||||
if (!!path.parse(content).ext) {
|
||||
// The content is a path to the document
|
||||
return readFile(path.resolve(content), { encoding: "utf8"});
|
||||
} else {
|
||||
// Content is the real document content
|
||||
return Promise.resolve(content);
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
const parsedContents = await Promise.all(rawContentsPromises);
|
||||
const pemParsedContents = parsedContents.map((file, index) => {
|
||||
const certName = Object.keys(options)[index];
|
||||
const pem = parsePEM(
|
||||
certName,
|
||||
file,
|
||||
typeof options.signerKey === "object"
|
||||
? options.signerKey.passphrase
|
||||
: undefined
|
||||
);
|
||||
|
||||
if (!pem) {
|
||||
throw new Error(formatMessage("INVALID_CERTS", certName));
|
||||
}
|
||||
|
||||
return { [certName]: pem };
|
||||
});
|
||||
|
||||
return Object.assign({}, ...pemParsedContents);
|
||||
} catch (err) {
|
||||
if (!err.path) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
throw new Error(formatMessage("INVALID_CERT_PATH", path.parse(err.path).base));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the PEM-formatted passed text (certificates)
|
||||
*
|
||||
* @param element - Text content of .pem files
|
||||
* @param passphrase - passphrase for the key
|
||||
* @returns The parsed certificate or key in node forge format
|
||||
*/
|
||||
|
||||
function parsePEM(pemName: string, element: string, passphrase?: string) {
|
||||
if (pemName === "signerKey" && passphrase) {
|
||||
return forge.pki.decryptRsaPrivateKey(element, String(passphrase));
|
||||
} else {
|
||||
return forge.pki.certificateFromPem(element);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { createPass };
|
||||
import { Pass } from "./pass";
|
||||
import { Certificates, isValid } from "./schema";
|
||||
|
||||
import { promisify } from "util";
|
||||
import { readFile as _readFile, readdir as _readdir } from "fs";
|
||||
import * as path from "path";
|
||||
import forge from "node-forge";
|
||||
import formatMessage from "./messages";
|
||||
|
||||
const readDir = promisify(_readdir);
|
||||
const readFile = promisify(_readFile);
|
||||
|
||||
interface FactoryOptions {
|
||||
model: string | { [key: string]: Buffer },
|
||||
certificates: Certificates;
|
||||
overrides?: Object;
|
||||
}
|
||||
|
||||
async function createPass(options: FactoryOptions) {
|
||||
if (!(options && Object.keys(options).length)) {
|
||||
throw new Error("Unable to create Pass: no options were passed");
|
||||
}
|
||||
|
||||
// Voglio leggere i certificati
|
||||
// Voglio leggere il model (se non è un oggetto)
|
||||
|
||||
/* Model checks */
|
||||
|
||||
if (!options.model) {
|
||||
throw new Error("Unable to create Pass: no model passed");
|
||||
}
|
||||
|
||||
if (typeof options.model !== "string" && typeof options.model !== "object") {
|
||||
throw new Error("Unable to create Pass: unsupported type");
|
||||
}
|
||||
|
||||
if (typeof options.model === "object" && !Object.keys(options.model).length) {
|
||||
throw new Error("Unable to create Pass: object model has no content");
|
||||
}
|
||||
|
||||
/* Certificates checks */
|
||||
|
||||
const { certificates } = await Promise.all([
|
||||
readCertificatesFromOptions(options.certificates)
|
||||
]);
|
||||
|
||||
// Controllo se il model è un oggetto o una stringa
|
||||
// Se è un oggetto passo avanti
|
||||
// Se è una stringa controllo se è un path. Se è un path
|
||||
// faccio readdir
|
||||
// altrimenti throw
|
||||
|
||||
// Creare una funzione che possa controllare ed estrarre i certificati
|
||||
// Creare una funzione che possa controllare ed estrarre i file
|
||||
// Entrambe devono ritornare Promise, così faccio await Promise.all
|
||||
|
||||
return new Pass();
|
||||
}
|
||||
|
||||
interface BundleUnit {
|
||||
[key: string]: Buffer;
|
||||
}
|
||||
|
||||
async function getModelContents(model: FactoryOptions["model"]) {
|
||||
if (!(model && (typeof model === "string" || (typeof model === "object" && Object.keys(model).length)))) {
|
||||
throw new Error("Unable to create Pass: invalid model provided");
|
||||
}
|
||||
|
||||
let modelContents: {
|
||||
bundle: BundleUnit,
|
||||
l10nBundle: {
|
||||
[key: string]: BundleUnit
|
||||
},
|
||||
};
|
||||
|
||||
if (typeof model === "string") {
|
||||
modelContents = await getModelFolderContents(model);
|
||||
} else {
|
||||
modelContents = getModelBufferContents(model);
|
||||
}
|
||||
|
||||
const modelFiles = Object.keys(modelContents);
|
||||
|
||||
if (!(modelFiles.includes("pass.json") && modelFiles.some(file => file.includes("icon")))) {
|
||||
throw new Error("missing icon or pass.json");
|
||||
}
|
||||
|
||||
return modelContents;
|
||||
}
|
||||
|
||||
async function getModelFolderContents(model: string) {
|
||||
const modelPath = path.resolve(model) + (!!model && !path.extname(model) ? ".pass" : "");
|
||||
const modelFilesList = await readDir(modelPath);
|
||||
|
||||
// No dot-starting files, manifest and signature
|
||||
const filteredFiles = removeHidden(modelFilesList).filter(f => !/(manifest|signature|pass)/i.test(f));
|
||||
|
||||
// Icon is required to proceed
|
||||
if (!(filteredFiles.length && filteredFiles.some(file => file.toLowerCase().includes("icon")))) {
|
||||
const eMessage = formatMessage("MODEL_UNINITIALIZED", path.parse(this.model).name);
|
||||
throw new Error(eMessage);
|
||||
}
|
||||
|
||||
// Splitting files from localization folders
|
||||
const bundle = filteredFiles.filter(entry => !entry.includes(".lproj"));
|
||||
const l10nFolders = filteredFiles.filter(entry => entry.includes(".lproj"));
|
||||
|
||||
const bundleBuffers = bundle.map(file => readFile(path.resolve(model, file)));
|
||||
const buffers = await Promise.all(bundleBuffers);
|
||||
|
||||
const bundleMap = Object.assign({},
|
||||
...bundle.map((fileName, index) => ({ [fileName]: buffers[index] }))
|
||||
) as BundleUnit;
|
||||
|
||||
// Reading concurrently localizations folder
|
||||
// and their files and their buffers
|
||||
const L10N_FilesListByFolder: Array<BundleUnit> = await Promise.all(
|
||||
l10nFolders.map(folderPath => {
|
||||
// Reading current folder
|
||||
const currentLangPath = path.join(model, folderPath);
|
||||
return readDir(currentLangPath)
|
||||
.then(files => {
|
||||
// Transforming files path to a model-relative path
|
||||
const validFiles = removeHidden(files)
|
||||
.map(file => path.join(currentLangPath, file));
|
||||
|
||||
// Getting all the buffers from file paths
|
||||
return Promise.all([
|
||||
...validFiles.map(file => readFile(file))
|
||||
]).then(buffers =>
|
||||
// Assigning each file path to its buffer
|
||||
Object.assign({}, ...validFiles.map((file, index) =>
|
||||
({ [file]: buffers[index] })
|
||||
)) as BundleUnit
|
||||
);
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
return {
|
||||
bundle: bundleMap,
|
||||
l10nBundle: Object.assign({}, ...L10N_FilesListByFolder
|
||||
.map((folder, index) => ({ [l10nFolders[index]]: folder }))
|
||||
) as { [key: string]: BundleUnit }
|
||||
};
|
||||
}
|
||||
|
||||
function getModelBufferContents(model: BundleUnit) {
|
||||
const bundle = removeHidden(Object.keys(model)).reduce<BundleUnit>((acc, current) => {
|
||||
// Checking if current file is one of the autogenerated ones or if its
|
||||
// content is not available
|
||||
if (/(manifest|signature)/.test(current) || !bundle[current]) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
return { ...acc, [current]: model[current] };
|
||||
}, {});
|
||||
|
||||
const bundleKeys = Object.keys(bundle);
|
||||
|
||||
if (!bundleKeys.length) {
|
||||
throw new Error("Cannot proceed with pass creation: bundle initialized")
|
||||
}
|
||||
|
||||
// separing localization folders
|
||||
const l10nFolders = bundleKeys.filter(file => file.includes(".lproj"));
|
||||
const l10nBundle: { [key: string]: BundleUnit } = Object.assign({},
|
||||
...l10nFolders.map(folder =>
|
||||
({ [folder]: bundle[folder] }) as BundleUnit
|
||||
)
|
||||
);
|
||||
|
||||
const unLocalizedBundle = Object.assign({},
|
||||
...bundleKeys
|
||||
.filter(file => !file.includes(".lproj"))
|
||||
.map(file => ({ [file]: bundle[file] }))
|
||||
) as BundleUnit;
|
||||
|
||||
return {
|
||||
bundle: unLocalizedBundle,
|
||||
l10nBundle
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads certificate contents, if the passed content is a path,
|
||||
* and parses them as a PEM.
|
||||
* @param options
|
||||
*/
|
||||
|
||||
interface FinalCertificates {
|
||||
wwdr: forge.pki.Certificate;
|
||||
signerCert: forge.pki.Certificate;
|
||||
signerKey: forge.pki.PrivateKey;
|
||||
}
|
||||
|
||||
async function readCertificatesFromOptions(options: Certificates): Promise<FinalCertificates> {
|
||||
if (!isValid(options, "certificatesSchema")) {
|
||||
throw new Error("Unable to create Pass: certificates schema validation failed.");
|
||||
}
|
||||
|
||||
// if the signerKey is an object, we want to get
|
||||
// all the real contents and don't care of passphrase
|
||||
const flattenedDocs = Object.assign({}, options, {
|
||||
signerKey: (
|
||||
typeof options.signerKey === "string"
|
||||
? options.signerKey
|
||||
: options.signerKey.keyFile
|
||||
)
|
||||
});
|
||||
|
||||
// We read the contents
|
||||
const rawContentsPromises = Object.keys(flattenedDocs)
|
||||
.map(content => {
|
||||
if (!!path.parse(content).ext) {
|
||||
// The content is a path to the document
|
||||
return readFile(path.resolve(content), { encoding: "utf8"});
|
||||
} else {
|
||||
// Content is the real document content
|
||||
return Promise.resolve(content);
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
const parsedContents = await Promise.all(rawContentsPromises);
|
||||
const pemParsedContents = parsedContents.map((file, index) => {
|
||||
const certName = Object.keys(options)[index];
|
||||
const pem = parsePEM(
|
||||
certName,
|
||||
file,
|
||||
typeof options.signerKey === "object"
|
||||
? options.signerKey.passphrase
|
||||
: undefined
|
||||
);
|
||||
|
||||
if (!pem) {
|
||||
throw new Error(formatMessage("INVALID_CERTS", certName));
|
||||
}
|
||||
|
||||
return { [certName]: pem };
|
||||
});
|
||||
|
||||
return Object.assign({}, ...pemParsedContents);
|
||||
} catch (err) {
|
||||
if (!err.path) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
throw new Error(formatMessage("INVALID_CERT_PATH", path.parse(err.path).base));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the PEM-formatted passed text (certificates)
|
||||
*
|
||||
* @param element - Text content of .pem files
|
||||
* @param passphrase - passphrase for the key
|
||||
* @returns The parsed certificate or key in node forge format
|
||||
*/
|
||||
|
||||
function parsePEM(pemName: string, element: string, passphrase?: string) {
|
||||
if (pemName === "signerKey" && passphrase) {
|
||||
return forge.pki.decryptRsaPrivateKey(element, String(passphrase));
|
||||
} else {
|
||||
return forge.pki.certificateFromPem(element);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { createPass };
|
||||
|
||||
Reference in New Issue
Block a user