1 | /**
|
2 | * Secure random string generator with custom alphabet.
|
3 | *
|
4 | * Alphabet must contain 256 symbols or less. Otherwise, the generator
|
5 | * will not be secure.
|
6 | *
|
7 | * @param {asyncGenerator} random The random bytes generator.
|
8 | * @param {string} alphabet Symbols to be used in new random string.
|
9 | * @param {size} size The number of symbols in new random string.
|
10 | *
|
11 | * @return {Promise} Promise with random string.
|
12 | *
|
13 | * @example
|
14 | * const formatAsync = require('nanoid/async/format')
|
15 | *
|
16 | * function random (size) {
|
17 | * const result = []
|
18 | * for (let i = 0; i < size; i++) {
|
19 | * result.push(randomByte())
|
20 | * }
|
21 | * return Promise.resolve(result)
|
22 | * }
|
23 | *
|
24 | * formatAsync(random, "abcdef", 5).then(id => {
|
25 | * model.id = id //=> "fbaef"
|
26 | * })
|
27 | *
|
28 | * @name formatAsync
|
29 | * @function
|
30 | */
|
31 | module.exports = function (random, alphabet, size) {
|
32 | // We can’t use bytes bigger than the alphabet. To make bytes values closer
|
33 | // to the alphabet, we apply bitmask on them. We look for the closest
|
34 | // `2 ** x - 1` number, which will be bigger than alphabet size. If we have
|
35 | // 30 symbols in the alphabet, we will take 31 (00011111).
|
36 | var mask = (2 << 31 - Math.clz32((alphabet.length - 1) | 1)) - 1
|
37 | // Bitmask is not a perfect solution (in our example it will pass 31 bytes,
|
38 | // which is bigger than the alphabet). As a result, we will need more bytes,
|
39 | // than ID size, because we will refuse bytes bigger than the alphabet.
|
40 |
|
41 | // Every hardware random generator call is costly,
|
42 | // because we need to wait for entropy collection. This is why often it will
|
43 | // be faster to ask for few extra bytes in advance, to avoid additional calls.
|
44 |
|
45 | // Here we calculate how many random bytes should we call in advance.
|
46 | // It depends on ID length, mask / alphabet size and magic number 1.6
|
47 | // (which was selected according benchmarks).
|
48 | var step = Math.ceil(1.6 * mask * size / alphabet.length)
|
49 |
|
50 | function tick (id) {
|
51 | return random(step).then(function (bytes) {
|
52 | // Compact alternative for `for (var i = 0; i < step; i++)`
|
53 | var i = step
|
54 | while (i--) {
|
55 | // If random byte is bigger than alphabet even after bitmask,
|
56 | // we refuse it by `|| ''`.
|
57 | id += alphabet[bytes[i] & mask] || ''
|
58 | // More compact than `id.length + 1 === size`
|
59 | if (id.length === +size) return id
|
60 | }
|
61 | return tick(id)
|
62 | })
|
63 | }
|
64 |
|
65 | return tick('')
|
66 | }
|
67 |
|
68 | /**
|
69 | * @callback asyncGenerator
|
70 | * @param {number} bytes The number of bytes to generate.
|
71 | * @return {Promise} Promise with array of random bytes.
|
72 | */
|