From a1a2978145169d6dda1a3cd25ac2de4f7be6ecb6 Mon Sep 17 00:00:00 2001 From: Xinyi Ye Date: Tue, 6 Jun 2023 09:15:53 -0700 Subject: [PATCH] fix: uuid collisions (#594) --- src/base64Id.js | 6 +++++- src/uuid.js | 36 ++++++++++++------------------------ test/base64Id.js | 9 +++++++++ test/uuid.js | 9 +++++++++ 4 files changed, 35 insertions(+), 25 deletions(-) diff --git a/src/base64Id.js b/src/base64Id.js index d111517b..ecffa4c5 100644 --- a/src/base64Id.js +++ b/src/base64Id.js @@ -2,10 +2,14 @@ const base64Chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'; const base64Id = () => { + const randomValues = crypto.getRandomValues(new Uint8Array(22)); + let str = ''; + for (let i = 0; i < 22; ++i) { - str += base64Chars.charAt(Math.floor(Math.random() * 64)); + str += base64Chars.charAt(randomValues[i] % 64); } + return str; }; diff --git a/src/uuid.js b/src/uuid.js index 4640de7a..928e5b66 100644 --- a/src/uuid.js +++ b/src/uuid.js @@ -1,34 +1,22 @@ /** - * Source: [jed's gist]{@link https://gist.github.com/982883}. + * Source: [jed's gist's comment]{@link https://gist.github.com/jed/982883?permalink_comment_id=3223002#gistcomment-3223002}. * Returns a random v4 UUID of the form xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx, * where each x is replaced with a random hexadecimal digit from 0 to f, and * y is replaced with a random hexadecimal digit from 8 to b. * Used to generate UUIDs for deviceIds. * @private */ -var uuid = function (a) { - return a // if the placeholder was passed, return - ? // a random number from 0 to 15 - ( - a ^ // unless b is 8, - ((Math.random() * // in which case - 16) >> // a random number from - (a / 4)) - ) // 8 to 11 - .toString(16) // in hexadecimal - : // or otherwise a concatenated string: - ( - [1e7] + // 10000000 + - -1e3 + // -1000 + - -4e3 + // -4000 + - -8e3 + // -80000000 + - -1e11 - ) // -100000000000, - .replace( - // replacing - /[018]/g, // zeroes, ones, and eights with - uuid, // random hex digits - ); + +// hoist hex table out of the function to avoid re-calculation +const hex = [...Array(256).keys()].map((index) => index.toString(16).padStart(2, '0')); + +var uuid = () => { + const r = crypto.getRandomValues(new Uint8Array(16)); + + r[6] = (r[6] & 0x0f) | 0x40; + r[8] = (r[8] & 0x3f) | 0x80; + + return [...r.entries()].map(([index, int]) => ([4, 6, 8, 10].includes(index) ? `-${hex[int]}` : hex[int])).join(''); }; export default uuid; diff --git a/test/base64Id.js b/test/base64Id.js index 3490be38..22a3616a 100644 --- a/test/base64Id.js +++ b/test/base64Id.js @@ -10,4 +10,13 @@ describe('base64Id', () => { it('should return an id of safe base64 characters', () => { assert.equal(true, /^[a-zA-Z0-9\-_]*$/.test(base64Id())); }); + + it('should generate a unique base64Id', () => { + const ids = new Set(); + const count = 10000; + for (let i = 0; i < count; i++) { + ids.add(base64Id()); + } + assert.equal(ids.size, count); + }); }); diff --git a/test/uuid.js b/test/uuid.js index 78c78017..601f6c6b 100644 --- a/test/uuid.js +++ b/test/uuid.js @@ -6,4 +6,13 @@ describe('UUID', function () { assert.equal(36, uuid.length); assert.equal('4', uuid.substring(14, 15)); }); + + it('should generate a unique UUID-4', () => { + const ids = new Set(); + const count = 10000; + for (let i = 0; i < count; i++) { + ids.add(UUID()); + } + assert.equal(ids.size, count); + }); });