Spaces:
Paused
Paused
File size: 3,614 Bytes
8c741f6 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 | /* 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;
|