Spaces:
Paused
Paused
| /* 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 | |
| : {}, | |
| random = Math.random, | |
| } = {}) => { | |
| const globals = Object.keys(globalObj).toString(); | |
| const sourceString = globals.length | |
| ? globals + createEntropy(bigLength, random) | |
| : createEntropy(bigLength, random); | |
| 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({ random }), | |
| } = {}) => { | |
| 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; | |