@walletpass/pass-js
Version:
Apple Wallet Pass generating and pushing updates from Node.js
190 lines • 9.18 kB
JavaScript
/**
* Base PassImages class to add image filePath manipulation
*/
;
Object.defineProperty(exports, "__esModule", { value: true });
const util_1 = require("util");
const path = require("path");
const fs_1 = require("fs");
const imagesize = require("imagesize");
const constants_1 = require("../constants");
const normalize_locale_1 = require("./normalize-locale");
const imageSize = util_1.promisify(imagesize);
const IMAGES_TYPES = new Set(Object.keys(constants_1.IMAGES));
exports.IMAGE_FILENAME_REGEX = new RegExp(`(^|/)((?<lang>[-A-Z_a-z]+).lproj/)?(?<imageType>${Object.keys(constants_1.IMAGES).join('|')}+)(@(?<density>[23]x))?.png$`);
class PassImages extends Map {
constructor(images) {
super(images instanceof PassImages ? [...images] : undefined);
}
async toArray() {
return Promise.all([...this].map(async ([filepath, pathOrBuffer]) => ({
path: filepath,
data: typeof pathOrBuffer === 'string'
? await fs_1.promises.readFile(pathOrBuffer)
: pathOrBuffer,
})));
}
/**
* Checks that all required images is set or throws elsewhere
*/
validate() {
const keys = [...this.keys()];
// Check for required images
for (const requiredImage of ['icon', 'logo'])
if (!keys.some(img => img.endsWith(`${requiredImage}.png`)))
throw new SyntaxError(`Missing required image ${requiredImage}.png`);
}
/**
* Load all images from the specified directory. Only supported images are
* loaded, nothing bad happens if directory contains other files.
*
* @param {string} dirPath - path to a directory with images
* @memberof PassImages
*/
async load(dirPath) {
var _a;
// Check if the path is accessible directory actually
const entries = await fs_1.promises.readdir(dirPath, { withFileTypes: true });
// checking rest of files
const entriesLoader = [];
for (const entry of entries) {
if (entry.isDirectory()) {
// check if it's a localization folder
const test = /(?<lang>[-A-Z_a-z]+)\.lproj/.exec(entry.name);
if (!((_a = test === null || test === void 0 ? void 0 : test.groups) === null || _a === void 0 ? void 0 : _a.lang))
continue;
const { lang } = test.groups;
// reading this directory
const currentPath = path.join(dirPath, entry.name);
const localizations = await fs_1.promises.readdir(currentPath, {
withFileTypes: true,
});
// check if we have any localized images
for (const f of localizations) {
const img = this.parseFilename(f.name);
if (img)
entriesLoader.push(this.add(img.imageType, path.join(currentPath, f.name), img.density, lang));
}
}
else {
// check it it's an image
const img = this.parseFilename(entry.name);
if (img)
entriesLoader.push(this.add(img.imageType, path.join(dirPath, entry.name), img.density));
}
}
await Promise.all(entriesLoader);
return this;
}
async add(imageType, pathOrBuffer, density, lang) {
if (!IMAGES_TYPES.has(imageType))
throw new TypeError(`Unknown image type ${imageSize} for ${imageType}`);
if (density && !constants_1.DENSITIES.has(density))
throw new TypeError(`Invalid density ${density} for ${imageType}`);
// check data
let sizeRes;
if (typeof pathOrBuffer === 'string') {
// PNG size is in first 24 bytes
const rs = fs_1.createReadStream(pathOrBuffer, { highWaterMark: 30 });
sizeRes = await imageSize(rs);
// see https://github.com/nodejs/node/issues/25335#issuecomment-451945106
rs.once('readable', () => rs.destroy());
}
else {
if (!Buffer.isBuffer(pathOrBuffer))
throw new TypeError(`Image data for ${imageType} must be either file path or buffer`);
const { Parser } = imagesize;
const parser = Parser();
const res = parser.parse(pathOrBuffer);
if (res !== Parser.DONE)
throw new TypeError(`Supplied buffer doesn't contain valid PNG image for ${imageType}`);
sizeRes = parser.getResult();
}
this.checkImage(imageType, sizeRes, density);
super.set(this.getImageFilename(imageType, density, lang), pathOrBuffer);
}
parseFilename(fileName) {
const test = exports.IMAGE_FILENAME_REGEX.exec(fileName);
if (!(test === null || test === void 0 ? void 0 : test.groups))
return undefined;
const res = { imageType: test.groups.imageType };
if (test.groups.density)
res.density = test.groups.density;
if (test.groups.lang)
res.lang = normalize_locale_1.normalizeLocale(test.groups.lang);
return res;
}
// eslint-disable-next-line complexity
checkImage(imageType, sizeResult, density) {
const densityMulti = density ? parseInt(density.charAt(0), 10) : 1;
const { format, width, height } = sizeResult;
if (format !== 'png')
throw new TypeError(`Image for "${imageType}" is not a PNG file!`);
if (!Number.isInteger(width) || width <= 0)
throw new TypeError(`Image ${imageType} has invalid width: ${width}`);
if (!Number.isInteger(height) || height <= 0)
throw new TypeError(`Image ${imageType} has invalid height: ${height}`);
/**
* @see {@link https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/PassKit_PG/Creating.html}
*/
switch (imageType) {
case 'icon':
if (width < 29 * densityMulti)
throw new TypeError(`icon image must have width ${29 *
densityMulti}px for ${densityMulti}x density`);
if (height < 29 * densityMulti)
throw new TypeError(`icon image must have height ${29 *
densityMulti}px for ${densityMulti}x density`);
break;
case 'logo':
if (width > 160 * densityMulti)
throw new TypeError(`logo image must have width no large than ${160 *
densityMulti}px for ${densityMulti}x density`);
// if (height > 50 * densityMulti)
// throw new TypeError(
// `logo image must have height ${50 *
// densityMulti}px for ${densityMulti}x density, received ${height}`,
// );
break;
case 'background':
if (width > 180 * densityMulti)
throw new TypeError(`background image must have width ${180 *
densityMulti}px for ${densityMulti}x density`);
if (height > 220 * densityMulti)
throw new TypeError(`background image must have height ${220 *
densityMulti}px for ${densityMulti}x density`);
break;
case 'footer':
if (width > 286 * densityMulti)
throw new TypeError(`footer image must have width ${286 *
densityMulti}px for ${densityMulti}x density`);
if (height > 15 * densityMulti)
throw new TypeError(`footer image must have height ${15 *
densityMulti}px for ${densityMulti}x density`);
break;
case 'strip':
// if (width > 375 * densityMulti)
// throw new TypeError(
// `strip image must have width ${375 *
// densityMulti}px for ${densityMulti}x density, received ${width}`,
// );
if (height > 144 * densityMulti)
throw new TypeError(`strip image must have height ${144 *
densityMulti}px for ${densityMulti}x density`);
break;
case 'thumbnail':
if (width > 120 * densityMulti)
throw new TypeError(`thumbnail image must have width no large than ${90 *
densityMulti}px for ${densityMulti}x density, received ${width}`);
if (height > 150 * densityMulti)
throw new TypeError(`thumbnail image must have height ${90 *
densityMulti}px for ${densityMulti}x density, received ${height}`);
break;
}
}
getImageFilename(imageType, density, lang) {
return `${lang ? `${normalize_locale_1.normalizeLocale(lang)}.lproj/` : ''}${imageType}${/^[23]x$/.test(density || '') ? `@${density}` : ''}.png`;
}
}
exports.PassImages = PassImages;
//# sourceMappingURL=images.js.map