function createError(message) { const err = new Error(message); err.source = "ulid"; return err; } // These values should NEVER change. If // they do, we're no longer making ulids! const ENCODING = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"; // Crockford's Base32 const ENCODING_LEN = ENCODING.length; const TIME_MAX = Math.pow(2, 48) - 1; const TIME_LEN = 10; const RANDOM_LEN = 16; export function replaceCharAt(str, index, char) { if (index > str.length - 1) { return str; } return str.substr(0, index) + char + str.substr(index + 1); } export function incrementBase32(str) { let done = undefined; let index = str.length; let char; let charIndex; const maxCharIndex = ENCODING_LEN - 1; while (!done && index-- >= 0) { char = str[index]; charIndex = ENCODING.indexOf(char); if (charIndex === -1) { throw createError("incorrectly encoded string"); } if (charIndex === maxCharIndex) { str = replaceCharAt(str, index, ENCODING[0]); continue; } done = replaceCharAt(str, index, ENCODING[charIndex + 1]); } if (typeof done === "string") { return done; } throw createError("cannot increment this string"); } export function randomChar(prng) { let rand = Math.floor(prng() * ENCODING_LEN); if (rand === ENCODING_LEN) { rand = ENCODING_LEN - 1; } return ENCODING.charAt(rand); } export function encodeTime(now, len) { if (isNaN(now)) { throw new Error(now + " must be a number"); } if (now > TIME_MAX) { throw createError("cannot encode time greater than " + TIME_MAX); } if (now < 0) { throw createError("time must be positive"); } if (Number.isInteger(now) === false) { throw createError("time must be an integer"); } let mod; let str = ""; for (; len > 0; len--) { mod = now % ENCODING_LEN; str = ENCODING.charAt(mod) + str; now = (now - mod) / ENCODING_LEN; } return str; } export function encodeRandom(len, prng) { let str = ""; for (; len > 0; len--) { str = randomChar(prng) + str; } return str; } export function decodeTime(id) { if (id.length !== TIME_LEN + RANDOM_LEN) { throw createError("malformed ulid"); } var time = id .substr(0, TIME_LEN) .split("") .reverse() .reduce((carry, char, index) => { const encodingIndex = ENCODING.indexOf(char); if (encodingIndex === -1) { throw createError("invalid character found: " + char); } return (carry += encodingIndex * Math.pow(ENCODING_LEN, index)); }, 0); if (time > TIME_MAX) { throw createError("malformed ulid, timestamp too large"); } return time; } export function detectPrng(allowInsecure = false, root) { if (!root) { root = typeof window !== "undefined" ? window : null; } const browserCrypto = root && (root.crypto || root.msCrypto); if (browserCrypto) { return () => { const buffer = new Uint8Array(1); browserCrypto.getRandomValues(buffer); return buffer[0] / 0xff; }; } else { try { const nodeCrypto = require("crypto"); return () => nodeCrypto.randomBytes(1).readUInt8() / 0xff; } catch (e) { } } if (allowInsecure) { try { console.error("secure crypto unusable, falling back to insecure Math.random()!"); } catch (e) { } return () => Math.random(); } throw createError("secure crypto unusable, insecure Math.random not allowed"); } export function factory(currPrng) { if (!currPrng) { currPrng = detectPrng(); } return function ulid(seedTime) { if (isNaN(seedTime)) { seedTime = Date.now(); } return encodeTime(seedTime, TIME_LEN) + encodeRandom(RANDOM_LEN, currPrng); }; } export function monotonicFactory(currPrng) { if (!currPrng) { currPrng = detectPrng(); } let lastTime = 0; let lastRandom; return function ulid(seedTime) { if (isNaN(seedTime)) { seedTime = Date.now(); } if (seedTime <= lastTime) { const incrementedRandom = (lastRandom = incrementBase32(lastRandom)); return encodeTime(lastTime, TIME_LEN) + incrementedRandom; } lastTime = seedTime; const newRandom = (lastRandom = encodeRandom(RANDOM_LEN, currPrng)); return encodeTime(seedTime, TIME_LEN) + newRandom; }; } export const ulid = factory();