UNPKG

3.97 kBJavaScriptView Raw
1// This file replaces `index.js` in bundlers like webpack or Rollup,
2// according to `browser` config in `package.json`.
3
4import { urlAlphabet } from './url-alphabet/index.js'
5
6if (process.env.NODE_ENV !== 'production') {
7 // All bundlers will remove this block in the production bundle.
8 if (
9 typeof navigator !== 'undefined' &&
10 navigator.product === 'ReactNative' &&
11 typeof crypto === 'undefined'
12 ) {
13 throw new Error(
14 'React Native does not have a built-in secure random generator. ' +
15 'If you don’t need unpredictable IDs use `nanoid/non-secure`. ' +
16 'For secure IDs, import `react-native-get-random-values` ' +
17 'before Nano ID. If you use Expo, install `expo-random` ' +
18 'and use `nanoid/async`.'
19 )
20 }
21 if (typeof msCrypto !== 'undefined' && typeof crypto === 'undefined') {
22 throw new Error(
23 'Add `if (!window.crypto) window.crypto = window.msCrypto` ' +
24 'before Nano ID to fix IE 11 support'
25 )
26 }
27 if (typeof crypto === 'undefined') {
28 throw new Error(
29 'Your browser does not have secure random generator. ' +
30 'If you don’t need unpredictable IDs, you can use nanoid/non-secure.'
31 )
32 }
33}
34
35let random = bytes => crypto.getRandomValues(new Uint8Array(bytes))
36
37let customRandom = (alphabet, size, getRandom) => {
38 // First, a bitmask is necessary to generate the ID. The bitmask makes bytes
39 // values closer to the alphabet size. The bitmask calculates the closest
40 // `2^31 - 1` number, which exceeds the alphabet size.
41 // For example, the bitmask for the alphabet size 30 is 31 (00011111).
42 // `Math.clz32` is not used, because it is not available in browsers.
43 let mask = (2 << (Math.log(alphabet.length - 1) / Math.LN2)) - 1
44 // Though, the bitmask solution is not perfect since the bytes exceeding
45 // the alphabet size are refused. Therefore, to reliably generate the ID,
46 // the random bytes redundancy has to be satisfied.
47
48 // Note: every hardware random generator call is performance expensive,
49 // because the system call for entropy collection takes a lot of time.
50 // So, to avoid additional system calls, extra bytes are requested in advance.
51
52 // Next, a step determines how many random bytes to generate.
53 // The number of random bytes gets decided upon the ID size, mask,
54 // alphabet size, and magic number 1.6 (using 1.6 peaks at performance
55 // according to benchmarks).
56
57 // `-~f => Math.ceil(f)` if f is a float
58 // `-~i => i + 1` if i is an integer
59 let step = -~((1.6 * mask * size) / alphabet.length)
60
61 return () => {
62 let id = ''
63 while (true) {
64 let bytes = getRandom(step)
65 // A compact alternative for `for (var i = 0; i < step; i++)`.
66 let j = step
67 while (j--) {
68 // Adding `|| ''` refuses a random byte that exceeds the alphabet size.
69 id += alphabet[bytes[j] & mask] || ''
70 // `id.length + 1 === size` is a more compact option.
71 if (id.length === +size) return id
72 }
73 }
74 }
75}
76
77let customAlphabet = (alphabet, size) => customRandom(alphabet, size, random)
78
79let nanoid = (size = 21) => {
80 let id = ''
81 let bytes = crypto.getRandomValues(new Uint8Array(size))
82
83 // A compact alternative for `for (var i = 0; i < step; i++)`.
84 while (size--) {
85 // It is incorrect to use bytes exceeding the alphabet size.
86 // The following mask reduces the random byte in the 0-255 value
87 // range to the 0-63 value range. Therefore, adding hacks, such
88 // as empty string fallback or magic numbers, is unneccessary because
89 // the bitmask trims bytes down to the alphabet size.
90 let byte = bytes[size] & 63
91 if (byte < 36) {
92 // `0-9a-z`
93 id += byte.toString(36)
94 } else if (byte < 62) {
95 // `A-Z`
96 id += (byte - 26).toString(36).toUpperCase()
97 } else if (byte < 63) {
98 id += '_'
99 } else {
100 id += '-'
101 }
102 }
103 return id
104}
105
106export { nanoid, customAlphabet, customRandom, urlAlphabet, random }