Permalink
Cannot retrieve contributors at this time
Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign up
Fetching contributors…
| 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(); |