UNPKG

devid

Version:

The most useful, developer friendly ID or token generator

104 lines (103 loc) 3.94 kB
"use strict"; 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;