asset-hash
Version:
Very fast asset hashing function for using e.g. during front-end deployments.
254 lines (214 loc) • 7 kB
JavaScript
/*! asset-hash v4.1.0 by Sebastian Werner <s.werner@sebastian-software.de> */
;
Object.defineProperty(exports, '__esModule', { value: true });
require('core-js/modules/es.error.cause.js');
var fs = require('fs');
var path = require('path');
var crypto = require('crypto');
var xxhash = require('xxhash-wasm');
var farmhash = require('farmhash');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e["default"] : e; }
var xxhash__default = /*#__PURE__*/_interopDefaultLegacy(xxhash);
function baseEncodeFactory(charset) {
const radix = BigInt(charset.length);
const decode = str => {
let result = BigInt(0);
for (const chr of str) {
const index = BigInt(charset.indexOf(chr));
result = radix * result + index;
}
return result;
};
const encode = (number, maxLength = Infinity) => {
let result = "";
while (number > 0) {
const mod = number % radix;
result = charset[Number(mod)] + result;
number = (number - mod) / radix;
if (result.length === maxLength) {
return result;
}
}
return result || `0`;
};
return {
decode,
encode
};
}
const baseEncoder = {
base26: baseEncodeFactory("abcdefghijklmnopqrstuvwxyz"),
base32: baseEncodeFactory("123456789abcdefghjkmnpqrstuvwxyz"),
// no 0lio
base36: baseEncodeFactory("0123456789abcdefghijklmnopqrstuvwxyz"),
base49: baseEncodeFactory("abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ"),
// no lIO
base52: baseEncodeFactory("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"),
base58: baseEncodeFactory("123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ"),
// no 0lIO
base62: baseEncodeFactory("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
};
function computeDigest(rawDigest, options = {}) {
let output = null;
const {
encoding,
maxLength
} = options;
const rawIsNumber = typeof rawDigest === "number" || typeof rawDigest === "bigint";
const rawIsBuffer = rawDigest instanceof Buffer; // Fast-path for number => hex
if (rawIsNumber && encoding === "hex") {
output = rawDigest.toString(16);
} else {
if (encoding === "base26" || encoding === "base32" || encoding === "base36" || encoding === "base49" || encoding === "base52" || encoding === "base58" || encoding === "base62") {
const valueAsBigInt = rawDigest instanceof Buffer ? BigInt(`0x${rawDigest.toString("hex")}`) : typeof rawDigest === "number" ? BigInt(rawDigest) : BigInt(rawDigest);
return baseEncoder[encoding].encode(valueAsBigInt, maxLength);
}
const valueAsBuffer = rawIsBuffer ? rawDigest : Buffer.from(rawDigest.toString());
output = valueAsBuffer.toString(encoding);
}
return maxLength != null ? output.slice(0, maxLength) : output;
}
let xxHashInstance;
let hasherReady = false;
async function initHashClasses() {
if (hasherReady) {
return;
} // Allow failing initialization for platforms < Node v16
try {
xxHashInstance = await xxhash__default();
} catch {}
hasherReady = true;
}
/**
* Make Node.js crypto hash signature compatible to Hash
*/
function cryptoEnvelope(hash) {
const envelopeHash = {
update: input => {
hash.update(input);
return envelopeHash;
},
digest: () => hash.digest()
};
return envelopeHash;
}
function farmhashEnvelope(hash) {
const data = [];
const envelopeHash = {
update: input => {
data.push(input);
return envelopeHash;
},
digest: () => {
const stringOrNumber = hash(Buffer.concat(data));
return BigInt(stringOrNumber);
}
};
return envelopeHash;
}
/**
* Creates hasher instance
*/
function createHasher(algorithm) {
if (!hasherReady) {
throw new Error("Hasher was not correctly initialized. Call `await initHashClasses()` first.");
}
let hasher;
if (algorithm === "xxhash32" || algorithm === "xxhash64") {
if (!xxHashInstance) {
throw new Error("Invalid xxhash WASM instance!");
}
hasher = algorithm === "xxhash32" ? xxHashInstance.create32() : xxHashInstance.create64();
} else if (algorithm === "farmhash32" || algorithm === "farmhash64") {
hasher = farmhashEnvelope(algorithm === "farmhash32" ? farmhash.fingerprint32 : farmhash.fingerprint64);
} else {
hasher = cryptoEnvelope(crypto.createHash(algorithm));
}
return hasher;
}
const NODE_MAJOR_VERSION = parseInt(process.versions.node.split('.')[0], 10);
const NODE_SUPPORTS_BIGINT_SINCE = 16;
const DEFAULT_ALGORITHM = NODE_MAJOR_VERSION < NODE_SUPPORTS_BIGINT_SINCE ? "farmhash64" : "xxhash64";
const DEFAULT_ENCODING = "base52";
const DEFAULT_MAX_LENGTH = 8;
class Hasher {
algorithm = DEFAULT_ALGORITHM;
encoding = DEFAULT_ENCODING;
maxLength = Infinity;
constructor(options = {}) {
if (options.algorithm) {
this.algorithm = options.algorithm;
}
if (options.encoding) {
this.encoding = options.encoding;
}
if (options.maxLength) {
this.maxLength = options.maxLength;
}
this.hasher = createHasher(this.algorithm);
}
update(data) {
const buffer = data instanceof Buffer ? data : Buffer.from(data.toString(), "utf-8");
this.hasher.update(buffer);
return this;
}
digest(encoding, maxLength) {
return computeDigest(this.hasher.digest(), {
encoding: encoding || this.encoding,
maxLength: maxLength || this.maxLength
});
}
}
/**
* Get hash from file
*
* @param fileName Filename of file to hash
*/
function getHash(fileName, options) {
const {
algorithm,
encoding,
maxLength
} = options || {};
return new Promise(async (resolve, reject) => {
try {
await initHashClasses();
const hasher = createHasher(algorithm || DEFAULT_ALGORITHM);
fs.createReadStream(fileName).on("data", data => {
hasher.update(data);
}).on("error", error => {
reject(error);
}).on("end", () => {
try {
const digest = computeDigest(hasher.digest(), {
encoding: encoding || DEFAULT_ENCODING,
maxLength: maxLength || DEFAULT_MAX_LENGTH
});
digest ? resolve(digest) : reject(new Error("Unexpected error while generating digest!"));
} catch (error) {
reject(error);
}
});
} catch (error) {
reject(error);
}
});
}
/**
* Get new filename based upon hash and extension of file
*
* @param fileName Filename of file to hash
*/
async function getHashedName(fileName, options) {
const hashed = await getHash(fileName, options);
const extension = path.extname(fileName);
return hashed + extension;
}
exports.DEFAULT_ALGORITHM = DEFAULT_ALGORITHM;
exports.DEFAULT_ENCODING = DEFAULT_ENCODING;
exports.DEFAULT_MAX_LENGTH = DEFAULT_MAX_LENGTH;
exports.Hasher = Hasher;
exports.getHash = getHash;
exports.getHashedName = getHashedName;
exports.initHashClasses = initHashClasses;
//# sourceMappingURL=index.cjs.js.map