Skip to content

Commit 2c93f5c

Browse files
authored
Merge pull request #251 from mixpanel/uuidv4
use Crypto API for UUID when available, otherwise still generate UUIDv4
2 parents df60d86 + 603f1da commit 2c93f5c

File tree

3 files changed

+49
-65
lines changed

3 files changed

+49
-65
lines changed

src/utils.js

+20-64
Original file line numberDiff line numberDiff line change
@@ -839,71 +839,27 @@ _.utf8Encode = function(string) {
839839
return utftext;
840840
};
841841

842-
_.UUID = (function() {
843-
844-
// Time-based entropy
845-
var T = function() {
846-
var time = 1 * new Date(); // cross-browser version of Date.now()
847-
var ticks;
848-
if (window.performance && window.performance.now) {
849-
ticks = window.performance.now();
850-
} else {
851-
// fall back to busy loop
852-
ticks = 0;
853-
854-
// this while loop figures how many browser ticks go by
855-
// before 1*new Date() returns a new number, ie the amount
856-
// of ticks that go by per millisecond
857-
while (time == 1 * new Date()) {
858-
ticks++;
859-
}
860-
}
861-
return time.toString(16) + Math.floor(ticks).toString(16);
862-
};
863-
864-
// Math.Random entropy
865-
var R = function() {
866-
return Math.random().toString(16).replace('.', '');
867-
};
868-
869-
// User agent entropy
870-
// This function takes the user agent string, and then xors
871-
// together each sequence of 8 bytes. This produces a final
872-
// sequence of 8 bytes which it returns as hex.
873-
var UA = function() {
874-
var ua = userAgent,
875-
i, ch, buffer = [],
876-
ret = 0;
877-
878-
function xor(result, byte_array) {
879-
var j, tmp = 0;
880-
for (j = 0; j < byte_array.length; j++) {
881-
tmp |= (buffer[j] << j * 8);
882-
}
883-
return result ^ tmp;
884-
}
885-
886-
for (i = 0; i < ua.length; i++) {
887-
ch = ua.charCodeAt(i);
888-
buffer.unshift(ch & 0xFF);
889-
if (buffer.length >= 4) {
890-
ret = xor(ret, buffer);
891-
buffer = [];
892-
}
893-
}
894-
895-
if (buffer.length > 0) {
896-
ret = xor(ret, buffer);
842+
_.UUID = function() {
843+
try {
844+
// use native Crypto API when available
845+
return window['crypto']['randomUUID']();
846+
} catch (err) {
847+
// fall back to generating our own UUID
848+
// based on https://gist.github.com/scwood/3bff42cc005cc20ab7ec98f0d8e1d59d
849+
var uuid = new Array(36);
850+
for (var i = 0; i < 36; i++) {
851+
uuid[i] = Math.floor(Math.random() * 16);
897852
}
898-
899-
return ret.toString(16);
900-
};
901-
902-
return function() {
903-
var se = (screen.height * screen.width).toString(16);
904-
return (T() + '-' + R() + '-' + UA() + '-' + se + '-' + T());
905-
};
906-
})();
853+
uuid[14] = 4; // set bits 12-15 of time-high-and-version to 0100
854+
uuid[19] = uuid[19] &= ~(1 << 2); // set bit 6 of clock-seq-and-reserved to zero
855+
uuid[19] = uuid[19] |= (1 << 3); // set bit 7 of clock-seq-and-reserved to one
856+
uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
857+
858+
return _.map(uuid, function(x) {
859+
return x.toString(16);
860+
}).join('');
861+
}
862+
};
907863

908864
// _.isBlockedUA()
909865
// This is to block various web spiders from executing our JS and

src/window.js

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ if (typeof(window) === 'undefined') {
55
hostname: ''
66
};
77
win = {
8+
crypto: {randomUUID: function() {throw Error('unsupported');}},
89
navigator: { userAgent: '', onLine: true },
910
document: {
1011
createElement: function() { return {}; },

tests/unit/utils.js

+28-1
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,6 @@ describe('_.isBlockedUA', function() {
242242
});
243243
});
244244

245-
246245
describe('batchedThrottle', function () {
247246
let clock = null;
248247

@@ -335,3 +334,31 @@ describe('batchedThrottle', function () {
335334
expect(returnVal).to.equal(`rejected 1,2,3`);
336335
});
337336
});
337+
338+
describe(`_.UUID`, function() {
339+
context(`when the environment supports the crypto API`, function() {
340+
beforeEach(function() {
341+
sinon.stub(window.crypto, `randomUUID`).returns(`fake-uuid`);
342+
});
343+
344+
afterEach(function() {
345+
sinon.restore();
346+
});
347+
348+
it(`uses the native randomUUID function`, function() {
349+
expect(_.UUID()).to.equal(`fake-uuid`);
350+
});
351+
});
352+
353+
context(`when the environment does not support the crypto API`, function() {
354+
it(`generates a unique 36-char UUID`, function() {
355+
const generatedIds = new Set();
356+
for (let i = 0; i < 100; i++) {
357+
const uuid = _.UUID();
358+
expect(uuid).to.match(/^[a-f0-9\-]{36}$/);
359+
generatedIds.add(uuid);
360+
}
361+
expect(generatedIds.size).to.equal(100);
362+
});
363+
});
364+
});

0 commit comments

Comments
 (0)