From 57c3876f96ecde38080519806f3fb2f476227768 Mon Sep 17 00:00:00 2001 From: Alexander Cerutti Date: Sun, 10 Oct 2021 02:46:23 +0200 Subject: [PATCH] Deleted file pass.ts --- src/pass.ts | 690 ---------------------------------------------------- 1 file changed, 690 deletions(-) delete mode 100644 src/pass.ts diff --git a/src/pass.ts b/src/pass.ts deleted file mode 100644 index 553631f..0000000 --- a/src/pass.ts +++ /dev/null @@ -1,690 +0,0 @@ -import path from "path"; -import forge from "node-forge"; -import debug from "debug"; -import { Stream } from "stream"; -import { ZipFile } from "yazl"; -import type Joi from "joi"; - -import * as Schemas from "./schemas"; -import formatMessage, { ERROR, DEBUG } from "./messages"; -import FieldsArray from "./fieldsArray"; -import { - generateStringFile, - dateToW3CString, - isValidRGB, - deletePersonalization, - getAllFilesWithName, -} from "./utils"; -import * as Signature from "./signature"; -import { processDate } from "./processDate"; - -const barcodeDebug = debug("passkit:barcode"); -const genericDebug = debug("passkit:generic"); - -const transitType = Symbol("transitType"); -const passProps = Symbol("_props"); - -const propsSchemaMap = new Map>([ - ["barcodes", Schemas.Barcode], - ["barcode", Schemas.Barcode], - ["beacons", Schemas.Beacon], - ["locations", Schemas.Location], - ["nfc", Schemas.NFC], -]); - -export class Pass { - private bundle: Schemas.BundleUnit; - private l10nBundles: Schemas.PartitionedBundle["l10nBundle"]; - private _fields: (keyof Schemas.PassFields)[] = [ - "primaryFields", - "secondaryFields", - "auxiliaryFields", - "backFields", - "headerFields", - ]; - private [passProps]: Schemas.ValidPass = {}; - private type: keyof Schemas.ValidPassType; - private fieldsKeys: Set = new Set(); - private passCore: Schemas.ValidPass; - - public headerFields: FieldsArray; - public primaryFields: FieldsArray; - public secondaryFields: FieldsArray; - public auxiliaryFields: FieldsArray; - public backFields: FieldsArray; - - private Certificates: Schemas.CertificatesSchema; - private [transitType]: string = ""; - private l10nTranslations: { - [languageCode: string]: { [placeholder: string]: string }; - } = {}; - - constructor(options: Schemas.PassInstance) { - if (!Schemas.isValid(options, Schemas.PassInstance)) { - throw new Error(formatMessage(ERROR.REQUIR_VALID_FAILED)); - } - - this.Certificates = options.certificates; - this.l10nBundles = options.model.l10nBundle; - this.bundle = { ...options.model.bundle }; - - try { - this.passCore = JSON.parse( - this.bundle["pass.json"].toString("utf8"), - ); - } catch (err) { - throw new Error(formatMessage(ERROR.PASSFILE_VALIDATION_FAILED)); - } - - // Parsing the options and extracting only the valid ones. - const validOverrides = Schemas.getValidated( - options.overrides || {}, - Schemas.OverridesSupportedOptions, - ); - - if (validOverrides === null) { - throw new Error(formatMessage(ERROR.OVV_KEYS_BADFORMAT)); - } - - this.type = Object.keys(this.passCore).find((key) => - /(boardingPass|eventTicket|coupon|generic|storeCard)/.test(key), - ) as keyof Schemas.ValidPassType; - - if (!this.type) { - throw new Error(formatMessage(ERROR.NO_PASS_TYPE)); - } - - // Parsing and validating pass.json keys - const passCoreKeys = Object.keys( - this.passCore, - ) as (keyof Schemas.ValidPass)[]; - - const validatedPassKeys = passCoreKeys.reduce( - (acc, current) => { - if (this.type === current) { - // We want to exclude type keys (eventTicket, - // boardingPass, ecc.) and their content - return acc; - } - - if (!propsSchemaMap.has(current)) { - // If the property is unknown (we don't care if - // it is valid or not for Wallet), we return - // directly the content - return { ...acc, [current]: this.passCore[current] }; - } - - const currentSchema = propsSchemaMap.get(current)!; - - if (Array.isArray(this.passCore[current])) { - const valid = getValidInArray( - currentSchema, - this.passCore[current] as Schemas.ArrayPassSchema[], - ); - - return { - ...acc, - [current]: valid, - }; - } else { - return { - ...acc, - [current]: - (Schemas.isValid( - this.passCore[current], - currentSchema, - ) && - this.passCore[current]) || - undefined, - }; - } - }, - {}, - ); - - this[passProps] = { - ...(validatedPassKeys || {}), - ...(validOverrides || {}), - }; - - if ( - this.type === "boardingPass" && - this.passCore[this.type]["transitType"] - ) { - // We might want to generate a boarding pass without setting manually - // in the code the transit type but right in the model; - this[transitType] = this.passCore[this.type]["transitType"]; - } - - this._fields.forEach((fieldName) => { - this[fieldName] = new FieldsArray( - this.fieldsKeys, - ...(this.passCore[this.type][fieldName] || []).filter((field) => - Schemas.isValid(field, Schemas.Field), - ), - ); - }); - } - - /** - * Generates the pass Stream - * - * @method generate - * @return A Stream of the generated pass. - */ - - generate(): Stream { - // Editing Pass.json - this.bundle["pass.json"] = this._patch(this.bundle["pass.json"]); - - /** - * Checking Personalization, as this is available only with NFC - * @see https://apple.co/2SHfb22 - */ - const currentBundleFiles = Object.keys(this.bundle); - - if ( - !this[passProps].nfc && - currentBundleFiles.includes("personalization.json") - ) { - genericDebug(formatMessage(DEBUG.PRS_REMOVED)); - deletePersonalization( - this.bundle, - getAllFilesWithName( - "personalizationLogo", - currentBundleFiles, - "startsWith", - ), - ); - } - - const finalBundle: Schemas.BundleUnit = { ...this.bundle }; - - /** - * Iterating through languages and generating pass.string file - */ - - const translationsLanguageCodes = Object.keys(this.l10nTranslations); - - for ( - let langs = translationsLanguageCodes.length, lang: string; - (lang = translationsLanguageCodes[--langs]); - - ) { - const strings = generateStringFile(this.l10nTranslations[lang]); - const languageBundleDirname = `${lang}.lproj`; - - if (strings.length) { - /** - * if there's already a buffer of the same folder and called - * `pass.strings`, we'll merge the two buffers. We'll create - * it otherwise. - */ - - const languageBundleUnit = (this.l10nBundles[ - languageBundleDirname - ] ??= {}); - - languageBundleUnit["pass.strings"] = Buffer.concat([ - languageBundleUnit["pass.strings"] || Buffer.alloc(0), - strings, - ]); - } - - if ( - !this.l10nBundles[languageBundleDirname] || - !Object.keys(this.l10nBundles[languageBundleDirname]).length - ) { - continue; - } - - /** - * Assigning all the localization files to the final bundle - * by mapping the buffer to the pass-relative file path; - * - * We are replacing the slashes to avoid Windows slashes - * composition. - */ - - const bundleRelativeL10NPaths = Object.entries( - this.l10nBundles[languageBundleDirname], - ).reduce((acc, [fileName, fileContent]) => { - const fullPath = path - .join(languageBundleDirname, fileName) - .replace(/\\/, "/"); - - return { - ...acc, - [fullPath]: fileContent, - }; - }, {}); - - Object.assign(finalBundle, bundleRelativeL10NPaths); - } - - /* - * Parsing the buffers, pushing them into the archive - * and returning the compiled manifest - */ - const archive = new ZipFile(); - const manifest = Object.entries(finalBundle).reduce( - (acc, [fileName, buffer]) => { - let hashFlow = forge.md.sha1.create(); - - hashFlow.update(buffer.toString("binary")); - archive.addBuffer(buffer, fileName); - - return { - ...acc, - [fileName]: hashFlow.digest().toHex(), - }; - }, - {}, - ); - - const signatureBuffer = Signature.create(manifest, this.Certificates); - - archive.addBuffer(signatureBuffer, "signature"); - archive.addBuffer( - Buffer.from(JSON.stringify(manifest)), - "manifest.json", - ); - const passStream = new Stream.PassThrough(); - - archive.outputStream.pipe(passStream); - archive.end(); - - return passStream; - } - - /** - * Adds traslated strings object to the list of translation to be inserted into the pass - * - * @method localize - * @params lang - the ISO 3166 alpha-2 code for the language - * @params translations - key/value pairs where key is the - * placeholder in pass.json localizable strings - * and value the real translated string. - * @returns {this} - * - * @see https://apple.co/2KOv0OW - Passes support localization - */ - - localize( - lang: string, - translations?: { [placeholder: string]: string }, - ): this { - if ( - lang && - typeof lang === "string" && - (typeof translations === "object" || translations === undefined) - ) { - this.l10nTranslations[lang] = translations || {}; - } - - return this; - } - - /** - * Sets expirationDate property to a W3C-formatted date - * - * @method expiration - * @params date - * @returns {this} - */ - - expiration(date: Date | null): this { - if (date === null) { - delete this[passProps]["expirationDate"]; - return this; - } - - const parsedDate = processDate("expirationDate", date); - - if (parsedDate) { - this[passProps]["expirationDate"] = parsedDate; - } - - return this; - } - - /** - * Sets voided property to true - * - * @method void - * @return {this} - */ - - void(): this { - this[passProps]["voided"] = true; - return this; - } - - /** - * Sets current pass' relevancy through beacons - * @param data varargs with type schema.Beacon, or single arg null - * @returns {Pass} - */ - - beacons(resetFlag: null): this; - beacons(...data: Schemas.Beacon[]): this; - beacons(...data: (Schemas.Beacon | null)[]): this { - if (data[0] === null) { - delete this[passProps]["beacons"]; - return this; - } - - const valid = getValidInArray(Schemas.Beacon, data); - - if (valid.length) { - this[passProps]["beacons"] = valid; - } - - return this; - } - - /** - * Sets current pass' relevancy through locations - * @param data varargs with type schema.Location, or single arg null - * @returns {Pass} - */ - - locations(resetFlag: null): this; - locations(...data: Schemas.Location[]): this; - locations(...data: (Schemas.Location | null)[]): this { - if (data[0] === null) { - delete this[passProps]["locations"]; - return this; - } - - const valid = getValidInArray(Schemas.Location, data); - - if (valid.length) { - this[passProps]["locations"] = valid; - } - - return this; - } - - /** - * Sets current pass' relevancy through a date - * @param data - * @returns {Pass} - */ - - relevantDate(date: Date | null): this { - if (date === null) { - delete this[passProps]["relevantDate"]; - return this; - } - - const parsedDate = processDate("relevantDate", date); - - if (parsedDate) { - this[passProps]["relevantDate"] = parsedDate; - } - - return this; - } - - /** - * Adds barcodes "barcodes" property. - * It allows to pass a string to autogenerate all the structures. - * - * @method barcode - * @params first - a structure or the string (message) that will generate - * all the barcodes - * @params data - other barcodes support - * @return {this} Improved this with length property and other methods - */ - - barcodes(resetFlag: null): this; - barcodes(message: string): this; - barcodes(...data: Schemas.Barcode[]): this; - barcodes(...data: (Schemas.Barcode | null | string)[]): this { - if (data[0] === null) { - delete this[passProps]["barcodes"]; - return this; - } - - if (typeof data[0] === "string") { - const autogen = barcodesFromUncompleteData(data[0]); - - if (!autogen.length) { - barcodeDebug(formatMessage(DEBUG.BRC_AUTC_MISSING_DATA)); - return this; - } - - this[passProps]["barcodes"] = autogen; - - return this; - } else { - /** - * Stripping from the array not-object elements - * and the ones that does not pass validation. - * Validation assign default value to missing parameters (if any). - */ - - const validBarcodes = data.reduce( - (acc, current) => { - if (!(current && current instanceof Object)) { - return acc; - } - - const validated = Schemas.getValidated( - current, - Schemas.Barcode, - ); - - if ( - !( - validated && - validated instanceof Object && - Object.keys(validated).length - ) - ) { - return acc; - } - - return [...acc, validated]; - }, - [], - ); - - if (validBarcodes.length) { - this[passProps]["barcodes"] = validBarcodes; - } - - return this; - } - } - - /** - * Given an index <= the amount of already set "barcodes", - * this let you choose which structure to use for retrocompatibility - * property "barcode". - * - * @method barcode - * @params format - the format to be used - * @return {this} - */ - - barcode(chosenFormat: Schemas.BarcodeFormat | null): this { - const { barcodes } = this[passProps]; - - if (chosenFormat === null) { - delete this[passProps]["barcode"]; - return this; - } - - if (typeof chosenFormat !== "string") { - barcodeDebug(formatMessage(DEBUG.BRC_FORMATTYPE_UNMATCH)); - return this; - } - - if (chosenFormat === "PKBarcodeFormatCode128") { - barcodeDebug(formatMessage(DEBUG.BRC_BW_FORMAT_UNSUPPORTED)); - return this; - } - - if (!(barcodes && barcodes.length)) { - barcodeDebug(formatMessage(DEBUG.BRC_NO_POOL)); - return this; - } - - // Checking which object among barcodes has the same format of the specified one. - const index = barcodes.findIndex((b) => - b.format.toLowerCase().includes(chosenFormat.toLowerCase()), - ); - - if (index === -1) { - barcodeDebug(formatMessage(DEBUG.BRC_NOT_SUPPORTED)); - return this; - } - - this[passProps]["barcode"] = barcodes[index]; - return this; - } - - /** - * Sets nfc fields in properties - * - * @method nfc - * @params data - the data to be pushed in the pass - * @returns {this} - * @see https://apple.co/2wTxiaC - */ - - nfc(data: Schemas.NFC | null): this { - if (data === null) { - delete this[passProps]["nfc"]; - return this; - } - - if ( - !( - data && - typeof data === "object" && - !Array.isArray(data) && - Schemas.isValid(data, Schemas.NFC) - ) - ) { - genericDebug(formatMessage(DEBUG.NFC_INVALID)); - return this; - } - - this[passProps]["nfc"] = data; - - return this; - } - - /** - * Allows to get the current inserted props; - * will return all props from valid overrides, - * template's pass.json and methods-inserted ones; - * - * @returns The properties will be inserted in the pass. - */ - - get props(): Readonly { - return this[passProps]; - } - - /** - * Edits the buffer of pass.json based on the passed options. - * - * @method _patch - * @params {Buffer} passBuffer - Buffer of the contents of pass.json - * @returns {Promise} Edited pass.json buffer or Object containing error. - */ - - private _patch(passCoreBuffer: Buffer): Buffer { - const passFile = JSON.parse( - passCoreBuffer.toString(), - ) as Schemas.ValidPass; - - if (Object.keys(this[passProps]).length) { - /* - * We filter the existing (in passFile) and non-valid keys from - * the below array keys that accept rgb values - * and then delete it from the passFile. - */ - - const passColors = [ - "backgroundColor", - "foregroundColor", - "labelColor", - ] as Array; - - passColors - .filter( - (v) => - this[passProps][v] && !isValidRGB(this[passProps][v]), - ) - .forEach((v) => delete this[passProps][v]); - - Object.assign(passFile, this[passProps]); - } - - this._fields.forEach((field) => { - passFile[this.type][field] = this[field]; - }); - - if (this.type === "boardingPass" && !this[transitType]) { - throw new Error(formatMessage(ERROR.TRSTYPE_REQUIRED)); - } - - passFile[this.type]["transitType"] = this[transitType]; - - return Buffer.from(JSON.stringify(passFile)); - } - - set transitType(value: string) { - if (!Schemas.isValid(value, Schemas.TransitType)) { - genericDebug(formatMessage(DEBUG.TRSTYPE_NOT_VALID, value)); - this[transitType] = this[transitType] || ""; - return; - } - - this[transitType] = value; - } - - get transitType(): string { - return this[transitType]; - } -} - -/** - * Automatically generates barcodes for all the types given common info - * - * @method barcodesFromUncompleteData - * @params message - the content to be placed inside "message" field - * @return Array of barcodeDict compliant - */ - -function barcodesFromUncompleteData(message: string): Schemas.Barcode[] { - if (!(message && typeof message === "string")) { - return []; - } - - return ( - [ - "PKBarcodeFormatQR", - "PKBarcodeFormatPDF417", - "PKBarcodeFormatAztec", - "PKBarcodeFormatCode128", - ] as Array - ).map((format) => - Schemas.getValidated({ format, message }, Schemas.Barcode), - ); -} - -function getValidInArray( - schemaName: Joi.ObjectSchema, - contents: T[], -): T[] { - return contents.filter( - (current) => - Object.keys(current).length && Schemas.isValid(current, schemaName), - ); -}