UNPKG

position-strings

Version:

Lexicographically-ordered position strings for collaborative lists and text

107 lines 4.51 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.IDs = void 0; const util_1 = require("./util"); /** * Utitilies for generating `PositionSource` IDs * (the `options.ID` constructor argument). */ class IDs { constructor() { // Not instantiable. } /** * Returns a cryptographically random ID made of alphanumeric characters. * * @param options.length The length of the ID, in characters. * Default: `IDs.DEFAULT_LENGTH`. * @param options.chars The characters to draw from. Default: `IDs.DEFAULT_CHARS`. * * If specified, only the first 256 elements are used, and you achieve * about `log_2(chars.length)` bits of entropy per `length`. */ static random(options) { const length = options?.length ?? this.DEFAULT_LENGTH; const chars = options?.chars ?? this.DEFAULT_CHARS; const arr = new Array(length); let randomValues = new Uint8Array(length); if (typeof window === "undefined") { // Use Node crypto library. // We use eval("require") to prevent Webpack from attempting // to bundle the crypto module and complaining. // In theory we should also be able to do this by // adding "browser": {"crypto": false} to package.json, // but that is not working, and besides, every user // of this package would have to remember to do so. // See https://github.com/webpack/webpack/issues/8826 const cryptoReal = (eval("require")("crypto")); const randomBuffer = cryptoReal.randomBytes(length); randomValues = new Uint8Array(randomBuffer); } else { // Use browser crypto library. window.crypto.getRandomValues(randomValues); } for (let i = 0; i < length; i++) { // This will be biased if chars.length does not divide 256, // but it will still give at least floor(log_2(chars.length)) // bits of entropy. arr[i] = chars[randomValues[i] % chars.length]; } return arr.join(""); } /** * Returns a psuedorandom ID made of alphanumeric characters, * generated using `rng` from package [seedrandom](https://www.npmjs.com/package/seedrandom). * * Pseudorandom IDs with a fixed seed are recommended for * tests and benchmarks, to make them deterministic. * * @param options.length The length of the ID, in characters. * Default: `IDs.DEFAULT_LENGTH`. * @param options.chars The characters to draw from. Default: `IDs.DEFAULT_CHARS`. * * If specified, only the first 256 elements are used, and you achieve * about `log_2(chars.length)` bits of entropy per `length`. */ static pseudoRandom(rng, options) { const length = options?.length ?? this.DEFAULT_LENGTH; const chars = options?.chars ?? this.DEFAULT_CHARS; const arr = new Array(length); for (let i = 0; i < arr.length; i++) { // Although we could pick chars without bias, we instead use the // same bias as `random`, for consistency. arr[i] = chars[Math.floor(rng() * 256) % chars.length]; } return arr.join(""); } /** * Throws an error if `ID` does not satisfy the * following requirements from `PositionSource`'s constructor: * - It does not contain `','` or `'.'`. * - The first character is lexicographically less than `'~'` (code point 126). */ static validate(ID) { (0, util_1.precond)(ID < util_1.LastInternal, "ID must be less than", util_1.LastInternal, ":", ID); (0, util_1.precond)(!ID.includes(","), "ID must not contain ',':", ID); (0, util_1.precond)(!ID.includes("."), "ID must not contain '.':", ID); } } exports.IDs = IDs; /** * Default characters used in IDs: alphanumeric chars. */ IDs.DEFAULT_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; // Rationale for value 8: // Each character of the ID gives us ~6 bits of entropy, // for a total of ~48 bits. This gives a < 1% // probability that two connected `PositionSource`s // will ever choose the same IDs, even if we // consider the total probability across 100,000,000 // documents with 1,000 IDs each // (= 10 users x 100 days x 1 ID/user/day). /** * The default length of an ID, in characters. */ IDs.DEFAULT_LENGTH = 8; //# sourceMappingURL=ids.js.map