armpit
Version:
Another resource manager programming interface toolkit.
174 lines • 6.1 kB
JavaScript
var _a;
import { createHash, createHmac } from "node:crypto";
import { CallableClassBase } from "./tsUtils.js";
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
export class NameHash extends CallableClassBase {
static getDefaultLength(type) {
switch (type) {
case "alphanumeric":
case "alpha":
return 3;
default:
return 4;
}
}
static calculateSha256Hash(values) {
const valuesHasher = createHash("sha256");
for (const value of values) {
if (typeof value === "string") {
valuesHasher.update(value, "utf8");
}
else {
valuesHasher.update(value);
}
}
return valuesHasher.digest();
}
static calculateSha256Hmac(prk, input) {
const hmac = createHmac("sha256", prk);
hmac.update(input);
const buffer = hmac.digest();
if (!(buffer.length > 0)) {
throw new Error("Hash failure");
}
return buffer;
}
static packBufferIntoInt(buffer) {
let result = 0n;
for (let i = 0; i < buffer.length; i++) {
result |= BigInt(buffer[i]) << BigInt(i * 8);
}
return result;
}
static toTextHex(data) {
let result = "";
for (let sourceIndex = 0, targetIndex = 0; sourceIndex < data.length; sourceIndex++, targetIndex += 2) {
const byte = data[sourceIndex];
result += (byte & 0xf).toString(16) + (byte >> 4).toString(16);
}
return result;
}
static radix26ToAlpha(value) {
let code = value.charCodeAt(0);
if (code >= 48 && code <= 57) {
code += 49;
}
else if ((code >= 97 && code <= 112) || (code >= 65 && code <= 80)) {
code += 10;
}
else {
return value;
}
return String.fromCharCode(code);
}
static toTextBaseN(data, type) {
let padCharacter = "0";
let resultLength;
let radix;
if (data.length !== 32) {
throw new Error("Unexpected data size");
}
switch (type) {
case "numeric":
resultLength = 77;
radix = 10;
break;
case "alpha":
resultLength = 54;
radix = 26;
padCharacter = "a";
break;
case "alphanumeric":
default:
resultLength = 49;
radix = 36;
break;
}
const radixEncoded = _a.packBufferIntoInt(data).toString(radix);
let result = "";
// Sometimes we may get an extra high order character out of the number that
// isn't fully covered by all the bits of data so it gets skipped.
const startIndex = Math.max(radixEncoded.length - resultLength, 0);
let i = radixEncoded.length - 1;
if (type === "alpha") {
for (; i >= startIndex; i--) {
result += _a.radix26ToAlpha(radixEncoded[i]);
}
}
else {
// Hashes of different lengths but sourced from the same inputs should sort together.
// Because the resulting values are effectively text which is sorted left to right,
// the lower order values should be on the left so that additional generated hash bytes
// are eventually appended to the right of the string. A simple reverse after building
// the value should work.
// Whole blocks are generated by this code so this may not matter technically, doing
// the reverse now gives more flexibility for future implementations, I hope.
for (; i >= startIndex; i--) {
result += radixEncoded[i];
}
}
// Sometimes the high order character maps to all zeros so padding is required
while (result.length < resultLength) {
result += padCharacter;
}
return result;
}
#values;
#options;
#cached;
constructor(...args) {
super();
let options = null;
if (args.length > 0 && typeof args[args.length - 1] !== "string") {
this.#values = args.slice(0, args.length - 1);
options = args[args.length - 1];
}
else {
this.#values = [...args];
options = null;
}
const type = options?.type ?? "alphanumeric";
this.#options = {
type,
defaultLength: Math.max(options?.defaultLength ?? _a.getDefaultLength(type), 1),
};
this.#cached = null;
}
concat(value) {
return new _a(...this.#values, value, this.#options);
}
toString(length) {
length = length != null && length > 0 ? length : this.#options.defaultLength;
let result = this.#cached;
if (result == null || result.length < length) {
result = this.#buildHashText(length);
this.#cached = result;
}
if (result.length > length) {
result = result.slice(0, length);
}
return result;
}
fnImpl(length) {
return this.toString(length);
}
#buildHashText(minTextLength) {
let hashValue = "";
let iteration = 1;
let tBuffer = null;
const pseudoRandomKey = _a.calculateSha256Hash(this.#values);
while (hashValue.length < minTextLength) {
let hmacInputBuffer = Buffer.from([iteration % 256]);
if (tBuffer) {
hmacInputBuffer = Buffer.concat([tBuffer, hmacInputBuffer]);
}
tBuffer = _a.calculateSha256Hmac(pseudoRandomKey, hmacInputBuffer);
hashValue +=
this.#options.type === "hex" ? _a.toTextHex(tBuffer) : _a.toTextBaseN(tBuffer, this.#options.type);
iteration++;
}
return hashValue;
}
}
_a = NameHash;
//# sourceMappingURL=nameHash.js.map