devid
Version:
The most useful, developer friendly ID or token generator
104 lines (103 loc) • 3.94 kB
JavaScript
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DevID = exports.DevIDError = void 0;
exports.setDelimiter = setDelimiter;
exports.devid = devid;
const node_crypto_1 = require("node:crypto");
const crc16ccitt_1 = __importDefault(require("crc/calculators/crc16ccitt"));
const errors_1 = require("./errors");
var errors_2 = require("./errors");
Object.defineProperty(exports, "DevIDError", { enumerable: true, get: function () { return errors_2.DevIDError; } });
const CROCKFORD_BASE_32 = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
const LINT_REGEX = new RegExp(`[^${CROCKFORD_BASE_32}]`, "g");
let delimiter = "_";
function encodeCrockfordBase32(buf, maxLength) {
const b32 = [];
const textBuf = BigInt("0x" + buf.toString("hex"))
.toString(2)
.padStart(buf.length * 8, "0");
let i = 0;
const length = maxLength || buf.length * 8;
while (i < length) {
b32.push(CROCKFORD_BASE_32[parseInt(textBuf.slice(i, i + 5), 2)]);
i += 5;
}
return b32.join("");
}
function decodeCrockfordBase32(encoded) {
const chars = encoded.split("");
const textBuf = chars
.map((char) => CROCKFORD_BASE_32.indexOf(char))
.reduce((str, pos) => str + pos.toString(2).padStart(5, "0"), "");
const length = Math.ceil((chars.length * 5) / 8);
const buf = Buffer.alloc(length);
for (let byte = 0; byte < length; byte++) {
const slice = textBuf.slice(byte * 8, (byte + 1) * 8).padEnd(8, "0");
buf[byte] = parseInt(slice, 2);
}
return buf;
}
function setDelimiter(newDelimiter) {
delimiter = newDelimiter;
}
class DevID {
constructor(devidOrPrefix) {
if (devidOrPrefix &&
(devidOrPrefix.includes(delimiter) || devidOrPrefix.length > 23)) {
const tokenParts = devidOrPrefix.split(delimiter);
const id = tokenParts.slice(-1)[0];
if (id.length > 24) {
throw new errors_1.DevIDError("FormatError");
}
const linted = id
.toUpperCase()
.replaceAll(/[oO]/g, "0")
.replaceAll(/[iIlL]/g, "1");
if (linted.match(LINT_REGEX)) {
throw new errors_1.DevIDError("FormatError");
}
const decodedDevid = decodeCrockfordBase32(linted);
const decodedChecksum = decodedDevid.readUInt16BE(13);
const calculatedChecksum = (0, crc16ccitt_1.default)(decodedDevid.subarray(0, 13));
if (decodedChecksum !== calculatedChecksum) {
throw new errors_1.DevIDError("ChecksumError");
}
if (tokenParts.length === 2) {
this.prefix = tokenParts[0];
}
this.token = linted;
}
else {
const time = BigInt(Math.floor((performance.timeOrigin + performance.now()) * 1000));
const timeBuf = Buffer.alloc(8);
timeBuf.writeBigInt64BE(time);
const finalBuf = Buffer.alloc(15);
timeBuf.copy(finalBuf, 0, 1);
(0, node_crypto_1.randomBytes)(6).copy(finalBuf, 7);
const crc = (0, crc16ccitt_1.default)(finalBuf.subarray(0, 13));
finalBuf.writeUInt16BE(crc, 13);
const b32 = encodeCrockfordBase32(finalBuf);
if (devidOrPrefix) {
this.prefix = devidOrPrefix;
}
this.token = b32;
}
}
toString() {
return this.prefix ? this.prefix + delimiter + this.token : this.token;
}
toJSON() {
return this.toString();
}
hasPrefix(prefix) {
return this.prefix === prefix;
}
}
exports.DevID = DevID;
function devid(devidOrPrefix) {
return new DevID(devidOrPrefix);
}
exports.default = devid;
;