1
- import { randomBytes } from "./deps.ts" ;
2
- import { promisify } from "./deps.ts" ;
3
1
import {
4
2
ALLOWED_TYPES ,
5
3
ALPHANUMERIC_CHARACTERS ,
@@ -8,40 +6,49 @@ import {
8
6
NUMERIC_CHARACTERS ,
9
7
URL_SAFE_CHARACTERS ,
10
8
} from "./constants.ts" ;
9
+ import { encodeToBase64 , encodeToHex } from "./deps.ts" ;
11
10
12
- const randomBytesAsync = promisify ( randomBytes ) ;
13
-
14
- const generateForCustomCharacters = ( length : number , characters : string [ ] ) => {
15
- // Generating entropy is faster than complex math operations, so we use the simplest way
16
- const characterCount = characters . length ;
17
- const maxValidSelector =
18
- ( Math . floor ( 0x10000 / characterCount ) * characterCount ) - 1 ; // Using values above this will ruin distribution when using modular division
19
- const entropyLength = 2 * Math . ceil ( 1.1 * length ) ; // Generating a bit more than required so chances we need more than one pass will be really low
20
- let string = "" ;
21
- let stringLength = 0 ;
22
-
23
- while ( stringLength < length ) { // In case we had many bad values, which may happen for character sets of size above 0x8000 but close to it
24
- const entropy = randomBytes ( entropyLength ) ;
25
- let entropyPosition = 0 ;
11
+ export interface GenerateRandomBytes {
12
+ (
13
+ byteLength : number ,
14
+ type : "hex" | "base64" ,
15
+ length : number ,
16
+ ) : string ;
17
+ }
18
+
19
+ export interface GenerateForCustomCharacters {
20
+ ( length : number , characters : string [ ] ) : string ;
21
+ }
22
+
23
+ export const MAX_RANDOM_VALUES = 65536 ;
24
+ export const MAX_SIZE = 4294967295 ;
25
+
26
+ function randomBytes ( size : number ) {
27
+ if ( size > MAX_SIZE ) {
28
+ throw new RangeError (
29
+ `The value of "size" is out of range. It must be >= 0 && <= ${ MAX_SIZE } . Received ${ size } ` ,
30
+ ) ;
31
+ }
26
32
27
- while ( entropyPosition < entropyLength && stringLength < length ) {
28
- const entropyValue = entropy . readUInt16LE ( entropyPosition ) ;
29
- entropyPosition += 2 ;
30
- if ( entropyValue > maxValidSelector ) { // Skip values which will ruin distribution when using modular division
31
- continue ;
32
- }
33
+ const bytes = new Uint8Array ( size ) ;
33
34
34
- string += characters [ entropyValue % characterCount ] ;
35
- stringLength ++ ;
35
+ //Work around for getRandomValues max generation
36
+ if ( size > MAX_RANDOM_VALUES ) {
37
+ for ( let generated = 0 ; generated < size ; generated += MAX_RANDOM_VALUES ) {
38
+ crypto . getRandomValues (
39
+ bytes . subarray ( generated , generated + MAX_RANDOM_VALUES ) ,
40
+ ) ;
36
41
}
42
+ } else {
43
+ crypto . getRandomValues ( bytes ) ;
37
44
}
38
45
39
- return string ;
40
- } ;
46
+ return bytes ;
47
+ }
41
48
42
- const generateForCustomCharactersAsync = async (
43
- length : number ,
44
- characters : string [ ] ,
49
+ const generateForCustomCharacters : GenerateForCustomCharacters = (
50
+ length ,
51
+ characters ,
45
52
) => {
46
53
// Generating entropy is faster than complex math operations, so we use the simplest way
47
54
const characterCount = characters . length ;
@@ -52,11 +59,16 @@ const generateForCustomCharactersAsync = async (
52
59
let stringLength = 0 ;
53
60
54
61
while ( stringLength < length ) { // In case we had many bad values, which may happen for character sets of size above 0x8000 but close to it
55
- const entropy = await randomBytesAsync ( entropyLength ) ; // eslint-disable-line no-await-in-loop
62
+ const entropy = randomBytes ( entropyLength ) ;
56
63
let entropyPosition = 0 ;
57
64
58
65
while ( entropyPosition < entropyLength && stringLength < length ) {
59
- const entropyValue = entropy . readUInt16LE ( entropyPosition ) ;
66
+ const entropyValue = new DataView (
67
+ entropy . buffer ,
68
+ entropy . byteOffset ,
69
+ entropy . byteLength ,
70
+ )
71
+ . getUint16 ( entropyPosition , true ) ;
60
72
entropyPosition += 2 ;
61
73
if ( entropyValue > maxValidSelector ) { // Skip values which will ruin distribution when using modular division
62
74
continue ;
@@ -70,24 +82,15 @@ const generateForCustomCharactersAsync = async (
70
82
return string ;
71
83
} ;
72
84
73
- const generateRandomBytes = (
74
- byteLength : number ,
75
- type : string ,
76
- length : number ,
77
- ) => randomBytes ( byteLength ) . toString ( type ) . slice ( 0 , length ) ;
78
-
79
- const generateRandomBytesAsync = async (
80
- byteLength : number ,
81
- type : string ,
82
- length : number ,
83
- ) => {
84
- const buffer = await randomBytesAsync ( byteLength ) ;
85
- return buffer . toString ( type ) . slice ( 0 , length ) ;
85
+ const generateRandomBytes : GenerateRandomBytes = ( byteLength , type , length ) => {
86
+ const bytes = randomBytes ( byteLength ) ;
87
+ const str = type === "base64" ? encodeToBase64 ( bytes ) : encodeToHex ( bytes ) ;
88
+ return str . slice ( 0 , length ) ;
86
89
} ;
87
90
88
91
const createGenerator = (
89
- generateForCustomCharacters : Function ,
90
- generateRandomBytes : Function ,
92
+ generateForCustomCharacters : GenerateForCustomCharacters ,
93
+ generateRandomBytes : GenerateRandomBytes ,
91
94
) =>
92
95
(
93
96
{ length, type, characters } : {
@@ -165,9 +168,5 @@ const cryptoRandomString = createGenerator(
165
168
generateForCustomCharacters ,
166
169
generateRandomBytes ,
167
170
) ;
168
- const cryptoRandomStringAsync = createGenerator (
169
- generateForCustomCharactersAsync ,
170
- generateRandomBytesAsync ,
171
- ) ;
172
171
173
- export { cryptoRandomString , cryptoRandomStringAsync } ;
172
+ export { cryptoRandomString } ;
0 commit comments