frontend/.pnpm-store/v3/files/39/39abcfb765e6d4b167d894821c7aa4645d262f02cffae4c74632d727a76d8c2587657ef3a38cb05a5039cea8c2ebcbacae2467b47c59a9ef89b7748020d9bf

127 lines
3.5 KiB
Plaintext

/* global global, window, module */
const { sha3_512: sha3 } = require("@noble/hashes/sha3");
const defaultLength = 24;
const bigLength = 32;
const createEntropy = (length = 4, random = Math.random) => {
let entropy = "";
while (entropy.length < length) {
entropy = entropy + Math.floor(random() * 36).toString(36);
}
return entropy;
};
/*
* Adapted from https://github.com/juanelas/bigint-conversion
* MIT License Copyright (c) 2018 Juan Hernández Serrano
*/
function bufToBigInt(buf) {
let bits = 8n;
let value = 0n;
for (const i of buf.values()) {
const bi = BigInt(i);
value = (value << bits) + bi;
}
return value;
}
const hash = (input = "") => {
// Drop the first character because it will bias the histogram
// to the left.
return bufToBigInt(sha3(input)).toString(36).slice(1);
};
const alphabet = Array.from({ length: 26 }, (x, i) =>
String.fromCharCode(i + 97)
);
const randomLetter = (random) =>
alphabet[Math.floor(random() * alphabet.length)];
/*
This is a fingerprint of the host environment. It is used to help
prevent collisions when generating ids in a distributed system.
If no global object is available, you can pass in your own, or fall back
on a random string.
*/
const createFingerprint = ({
globalObj = typeof global !== "undefined"
? global
: typeof window !== "undefined"
? window
: {},
} = {}) => {
const globals = Object.keys(globalObj).toString();
const sourceString = globals.length
? globals + createEntropy(bigLength)
: createEntropy(bigLength);
return hash(sourceString).substring(0, bigLength);
};
const createCounter = (count) => () => {
return count++;
};
// ~22k hosts before 50% chance of initial counter collision
// with a remaining counter range of 9.0e+15 in JavaScript.
const initialCountMax = 476782367;
const init = ({
// Fallback if the user does not pass in a CSPRNG. This should be OK
// because we don't rely solely on the random number generator for entropy.
// We also use the host fingerprint, current time, and a session counter.
random = Math.random,
counter = createCounter(Math.floor(random() * initialCountMax)),
length = defaultLength,
fingerprint = createFingerprint(),
} = {}) => {
return function cuid2() {
const firstLetter = randomLetter(random);
// If we're lucky, the `.toString(36)` calls may reduce hashing rounds
// by shortening the input to the hash function a little.
const time = Date.now().toString(36);
const count = counter().toString(36);
// The salt should be long enough to be globally unique across the full
// length of the hash. For simplicity, we use the same length as the
// intended id output.
const salt = createEntropy(length, random);
const hashInput = `${time + salt + count + fingerprint}`;
return `${firstLetter + hash(hashInput).substring(1, length)}`;
};
};
const createId = init();
const isCuid = (id, { minLength = 2, maxLength = bigLength } = {}) => {
const length = id.length;
const regex = /^[0-9a-z]+$/;
try {
if (
typeof id === "string" &&
length >= minLength &&
length <= maxLength &&
regex.test(id)
)
return true;
} finally {
}
return false;
};
module.exports.getConstants = () => ({ defaultLength, bigLength });
module.exports.init = init;
module.exports.createId = createId;
module.exports.bufToBigInt = bufToBigInt;
module.exports.createCounter = createCounter;
module.exports.createFingerprint = createFingerprint;
module.exports.isCuid = isCuid;