Skip to content

Commit a1a2978

Browse files
authored
fix: uuid collisions (#594)
1 parent d346cba commit a1a2978

4 files changed

Lines changed: 35 additions & 25 deletions

File tree

src/base64Id.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@
22
const base64Chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
33

44
const base64Id = () => {
5+
const randomValues = crypto.getRandomValues(new Uint8Array(22));
6+
57
let str = '';
8+
69
for (let i = 0; i < 22; ++i) {
7-
str += base64Chars.charAt(Math.floor(Math.random() * 64));
10+
str += base64Chars.charAt(randomValues[i] % 64);
811
}
12+
913
return str;
1014
};
1115

src/uuid.js

Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,22 @@
11
/**
2-
* Source: [jed's gist]{@link https://gist.github.com/982883}.
2+
* Source: [jed's gist's comment]{@link https://gist.github.com/jed/982883?permalink_comment_id=3223002#gistcomment-3223002}.
33
* Returns a random v4 UUID of the form xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx,
44
* where each x is replaced with a random hexadecimal digit from 0 to f, and
55
* y is replaced with a random hexadecimal digit from 8 to b.
66
* Used to generate UUIDs for deviceIds.
77
* @private
88
*/
9-
var uuid = function (a) {
10-
return a // if the placeholder was passed, return
11-
? // a random number from 0 to 15
12-
(
13-
a ^ // unless b is 8,
14-
((Math.random() * // in which case
15-
16) >> // a random number from
16-
(a / 4))
17-
) // 8 to 11
18-
.toString(16) // in hexadecimal
19-
: // or otherwise a concatenated string:
20-
(
21-
[1e7] + // 10000000 +
22-
-1e3 + // -1000 +
23-
-4e3 + // -4000 +
24-
-8e3 + // -80000000 +
25-
-1e11
26-
) // -100000000000,
27-
.replace(
28-
// replacing
29-
/[018]/g, // zeroes, ones, and eights with
30-
uuid, // random hex digits
31-
);
9+
10+
// hoist hex table out of the function to avoid re-calculation
11+
const hex = [...Array(256).keys()].map((index) => index.toString(16).padStart(2, '0'));
12+
13+
var uuid = () => {
14+
const r = crypto.getRandomValues(new Uint8Array(16));
15+
16+
r[6] = (r[6] & 0x0f) | 0x40;
17+
r[8] = (r[8] & 0x3f) | 0x80;
18+
19+
return [...r.entries()].map(([index, int]) => ([4, 6, 8, 10].includes(index) ? `-${hex[int]}` : hex[int])).join('');
3220
};
3321

3422
export default uuid;

test/base64Id.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,13 @@ describe('base64Id', () => {
1010
it('should return an id of safe base64 characters', () => {
1111
assert.equal(true, /^[a-zA-Z0-9\-_]*$/.test(base64Id()));
1212
});
13+
14+
it('should generate a unique base64Id', () => {
15+
const ids = new Set();
16+
const count = 10000;
17+
for (let i = 0; i < count; i++) {
18+
ids.add(base64Id());
19+
}
20+
assert.equal(ids.size, count);
21+
});
1322
});

test/uuid.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,13 @@ describe('UUID', function () {
66
assert.equal(36, uuid.length);
77
assert.equal('4', uuid.substring(14, 15));
88
});
9+
10+
it('should generate a unique UUID-4', () => {
11+
const ids = new Set();
12+
const count = 10000;
13+
for (let i = 0; i < count; i++) {
14+
ids.add(UUID());
15+
}
16+
assert.equal(ids.size, count);
17+
});
918
});

0 commit comments

Comments
 (0)