index.cjs 2.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071
  1. let crypto = require('crypto')
  2. let { urlAlphabet } = require('../url-alphabet/index.cjs')
  3. // `crypto.randomFill()` is a little faster than `crypto.randomBytes()`,
  4. // because it is possible to use in combination with `Buffer.allocUnsafe()`.
  5. let random = bytes =>
  6. new Promise((resolve, reject) => {
  7. // `Buffer.allocUnsafe()` is faster because it doesn’t flush the memory.
  8. // Memory flushing is unnecessary since the buffer allocation itself resets
  9. // the memory with the new bytes.
  10. crypto.randomFill(Buffer.allocUnsafe(bytes), (err, buf) => {
  11. if (err) {
  12. reject(err)
  13. } else {
  14. resolve(buf)
  15. }
  16. })
  17. })
  18. let customAlphabet = (alphabet, size) => {
  19. // First, a bitmask is necessary to generate the ID. The bitmask makes bytes
  20. // values closer to the alphabet size. The bitmask calculates the closest
  21. // `2^31 - 1` number, which exceeds the alphabet size.
  22. // For example, the bitmask for the alphabet size 30 is 31 (00011111).
  23. let mask = (2 << (31 - Math.clz32((alphabet.length - 1) | 1))) - 1
  24. // Though, the bitmask solution is not perfect since the bytes exceeding
  25. // the alphabet size are refused. Therefore, to reliably generate the ID,
  26. // the random bytes redundancy has to be satisfied.
  27. // Note: every hardware random generator call is performance expensive,
  28. // because the system call for entropy collection takes a lot of time.
  29. // So, to avoid additional system calls, extra bytes are requested in advance.
  30. // Next, a step determines how many random bytes to generate.
  31. // The number of random bytes gets decided upon the ID size, mask,
  32. // alphabet size, and magic number 1.6 (using 1.6 peaks at performance
  33. // according to benchmarks).
  34. let step = Math.ceil((1.6 * mask * size) / alphabet.length)
  35. let tick = id =>
  36. random(step).then(bytes => {
  37. // A compact alternative for `for (var i = 0; i < step; i++)`.
  38. let i = step
  39. while (i--) {
  40. // Adding `|| ''` refuses a random byte that exceeds the alphabet size.
  41. id += alphabet[bytes[i] & mask] || ''
  42. if (id.length === size) return id
  43. }
  44. return tick(id)
  45. })
  46. return () => tick('')
  47. }
  48. let nanoid = (size = 21) =>
  49. random(size).then(bytes => {
  50. let id = ''
  51. // A compact alternative for `for (var i = 0; i < step; i++)`.
  52. while (size--) {
  53. // It is incorrect to use bytes exceeding the alphabet size.
  54. // The following mask reduces the random byte in the 0-255 value
  55. // range to the 0-63 value range. Therefore, adding hacks, such
  56. // as empty string fallback or magic numbers, is unneccessary because
  57. // the bitmask trims bytes down to the alphabet size.
  58. id += urlAlphabet[bytes[size] & 63]
  59. }
  60. return id
  61. })
  62. module.exports = { nanoid, customAlphabet, random }