@shockpkg/ria-packager
Version:
Package for creating Adobe AIR packages
567 lines (439 loc) • 11.7 kB
JavaScript
import _defineProperty from "@babel/runtime/helpers/defineProperty";
import { join as pathJoin, basename } from 'path';
import fse from 'fs-extra';
import { ArchiveDir, ArchiveHdi, createArchiveByFileExtension, fsWalk, pathNormalize } from '@shockpkg/archive-files';
import { Signature } from "./signature.mjs";
import { HasherSha256 } from "./hasher/sha256.mjs";
/**
* Options for adding resources.
*/
/**
* Packager constructor.
*
* @param path Output path.
*/
export class Packager extends Object {
/**
* Make a debug build.
*/
/**
* Keystore object to use for signing.
*/
/**
* Timestamp URL.
*/
/**
* File and directory names to exclude when added a directory.
*/
/**
* Output path.
*/
/**
* Open flag.
*/
/**
* Adding a resource flag.
*/
/**
* Hasher object.
*/
/**
* Signature object.
*/
constructor(path) {
super();
_defineProperty(this, "debug", false);
_defineProperty(this, "keystore", null);
_defineProperty(this, "timestampUrl", null);
_defineProperty(this, "excludes", [/^\./, /^ehthumbs\.db$/, /^Thumbs\.db$/]);
_defineProperty(this, "path", void 0);
_defineProperty(this, "_isOpen", false);
_defineProperty(this, "_isAddingResource", false);
_defineProperty(this, "_hasher", void 0);
_defineProperty(this, "_signature", null);
this._hasher = this._createHasher();
this.path = path;
}
/**
* Check if output open.
*
* @returns Returns true if open, else false.
*/
get isOpen() {
return this._isOpen;
}
/**
* Open with application descriptor XML data.
*
* @param applicationData XML data.
*/
async open(applicationData) {
if (this._isOpen) {
throw new Error('Already open');
}
this._applicationInfoInit(applicationData);
await this._open(applicationData);
this._isOpen = true;
this._hasher.reset();
this._signature = null;
if (this.signed) {
this._signature = this._createSignature();
this._signature.timestampUrl = this.timestampUrl;
const keystore = this._getKeystore();
this._signature.certificate = keystore.getCertificate();
this._signature.keyPrivate = keystore.getKeyPrivate();
}
await this._addMetaResourcesStart(applicationData);
}
/**
* Open with application descriptor file.
*
* @param descriptorFile Application descriptor file.
*/
async openFile(descriptorFile) {
const applicationData = await fse.readFile(descriptorFile);
await this.open(applicationData);
}
/**
* Close output.
*/
async close() {
if (!this._isOpen) {
throw new Error('Not open');
}
await this._addMetaResourcesEnd();
await this._close();
this._isOpen = false;
this._applicationInfoClear();
this._hasher.reset();
this._signature = null;
}
/**
* Run asyncronous function with automatic open and close.
*
* @param applicationData XML data.
* @param func Async function.
* @returns Return value of the async function.
*/
async with(applicationData, func) {
await this.open(applicationData);
let r;
try {
r = await func.call(this, this);
} finally {
await this.close();
}
return r;
}
/**
* Run asyncronous function with automatic open and close.
*
* @param descriptorFile Application descriptor file.
* @param func Async function.
* @returns Return value of the async function.
*/
async withFile(descriptorFile, func) {
await this.openFile(descriptorFile);
let r;
try {
r = await func.call(this, this);
} finally {
await this.close();
}
return r;
}
/**
* Check if name is excluded file.
*
* @param name File name.
* @returns Returns true if excluded, else false.
*/
isExcludedFile(name) {
for (const exclude of this.excludes) {
if (exclude.test(name)) {
return true;
}
}
return false;
}
/**
* Add resource with file.
*
* @param source File path.
* @param destination Packaged file relative destination.
* @param options Resource options.
*/
async addResourceFile(source, destination = null, options = null) {
const opts = options || {};
const dest = destination === null ? source : destination;
const stat = await fse.lstat(source); // Symlinks would only be allowed in a macOS native extension.
// Throw an error like the official packager does.
if (stat.isSymbolicLink()) {
throw new Error(`Cannot add symlink: ${source}`);
} // Throw if not a regular file.
if (!stat.isFile()) {
throw new Error(`Unsupported file type: ${source}`);
}
let {
executable
} = opts;
if (executable !== true && executable !== false) {
// eslint-disable-next-line no-bitwise
executable = !!(stat.mode & 0b001000000);
}
const data = await fse.readFile(source);
await this.addResource(dest, data, {
executable,
mtime: opts.mtime || stat.mtime
});
}
/**
* Add resource with directory.
* Walks the directory looking for files to add, skips excluded file names.
*
* @param source Directory path.
* @param destination Packaged directory relative destination.
* @param options Resource options.
*/
async addResourceDirectory(source, destination = null, options = null) {
const dest = destination === null ? source : destination;
await fsWalk(source, async (path, stat) => {
// If this name is excluded, skip without descending.
if (this.isExcludedFile(basename(path))) {
return false;
} // Ignore directories, but descend into them.
// Only files are listed in the ZIP packages.
// Created automatically for files in any other package.
if (stat.isDirectory()) {
return true;
} // Anything else assume file.
await this.addResourceFile(pathJoin(source, path), pathJoin(dest, path), options);
return true;
});
}
/**
* Add resource with data.
*
* @param destination Packaged file relative destination.
* @param data Resource data.
* @param options Resource options.
*/
async addResource(destination, data, options = null) {
if (!this._isOpen) {
throw new Error('Not open');
}
if (this._isAddingResource) {
throw new Error('Resources must be added sequentially');
}
this._isAddingResource = true;
await this._addResource(pathNormalize(destination), data, options || {}, true, true);
this._isAddingResource = false;
}
/**
* Create Hasher object.
*
* @returns Hasher object.
*/
_createHasher() {
return new HasherSha256();
}
/**
* Create Signature object.
*
* @returns Hasher object.
*/
_createSignature() {
return new Signature();
}
/**
* Path of the mimetype meta resource.
*
* @returns Resource path.
*/
get _metaResourceMimetypePath() {
return 'mimetype';
}
/**
* Path of the application meta resource.
*
* @returns Resource path.
*/
get _metaResourceApplicationPath() {
return 'META-INF/AIR/application.xml';
}
/**
* Path of the hash meta resource.
*
* @returns Resource path.
*/
get _metaResourceHashPath() {
return 'META-INF/AIR/hash';
}
/**
* Path of the debug meta resource.
*
* @returns Resource path.
*/
get _metaResourceDebugPath() {
return 'META-INF/AIR/debug';
}
/**
* Path of the signatures meta resource.
*
* @returns Resource path.
*/
get _metaResourceSignaturesPath() {
return 'META-INF/signatures.xml';
}
/**
* Get encoded mimetype data.
*
* @returns Mimetype buffer.
*/
_getMimetypeData() {
// The mimetype if UTF-8.
return Buffer.from(this.mimetype, 'utf8');
}
/**
* Get the keystore object.
*
* @returns Keystore object.
*/
_getKeystore() {
const r = this.keystore;
if (!r) {
throw new Error('A keystore not set');
}
return r;
}
/**
* Add resource with data, with options controlling hashing and signing.
*
* @param destination Packaged file relative destination.
* @param data Resource data.
* @param options Resource options.
* @param hashed This file is hashed.
* @param signed This file is signed.
*/
async _addResource(destination, data, options, hashed, signed) {
if (hashed) {
this._hasher.update(data);
}
if (signed) {
const signature = this._signature;
if (signature) {
signature.addFile(destination, data);
}
}
await this._writeResource(destination, data, options);
}
/**
* Add meta resources start.
*
* @param applicationData XML data.
*/
async _addMetaResourcesStart(applicationData) {
await this._addMetaResourceMimetype();
await this._addMetaResourceApplication(applicationData);
await this._addMetaResourceHash();
if (this.debug) {
await this._addMetaResourceDebug();
}
}
/**
* Add meta resources end.
*/
async _addMetaResourcesEnd() {
if (this.signed) {
await this._addMetaResourceSignatures();
}
}
/**
* Add meta resource for the mimetype.
*/
async _addMetaResourceMimetype() {
const path = this._metaResourceMimetypePath;
const data = this._getMimetypeData();
await this._addResource(path, data, {}, true, true);
}
/**
* Add meta resource for the application descriptor.
*
* @param applicationData The application descriptor data.
*/
async _addMetaResourceApplication(applicationData) {
const path = this._metaResourceApplicationPath;
await this._addResource(path, applicationData, {}, true, true);
}
/**
* Add meta resource for the hash (needs updating on close).
*/
async _addMetaResourceHash() {
const path = this._metaResourceHashPath;
const data = Buffer.alloc(this._hasher.bytes);
await this._addResource(path, data, {}, false, false);
}
/**
* Add meta resource for debug.
*/
async _addMetaResourceDebug() {
const path = this._metaResourceDebugPath;
const data = Buffer.alloc(0);
await this._addResource(path, data, {}, true, true);
}
/**
* Add resource for signatures.
*/
async _addMetaResourceSignatures() {
const path = this._metaResourceSignaturesPath;
const signature = this._signature;
if (!signature) {
throw new Error('Internal error');
}
signature.digest();
signature.sign();
if (signature.timestampUrl) {
await signature.timestamp();
}
const data = signature.encode();
await this._addResource(path, data, {}, false, false);
}
/**
* Init application info from descriptor data.
*
* @param applicationData The application descriptor data.
*/
_applicationInfoInit(applicationData) {// Do nothing.
}
/**
* Clear application info from descriptor data.
*/
_applicationInfoClear() {// Do nothing.
}
/**
* Open path as archive.
*
* @param path Archive path.
* @returns Archive instance.
*/
async _openArchive(path) {
const stat = await fse.stat(path);
if (stat.isDirectory()) {
return new ArchiveDir(path);
}
const archive = createArchiveByFileExtension(path);
if (!archive) {
throw new Error(`Unrecognized archive format: ${path}`);
}
if (archive instanceof ArchiveHdi) {
archive.nobrowse = true;
}
return archive;
}
/**
* Package mimetype.
*
* @returns Mimetype string.
*/
}
//# sourceMappingURL=packager.mjs.map