Skip to content

Commit 42ece80

Browse files
committed
fix(core): Improve uuid performance
1 parent c68674a commit 42ece80

File tree

2 files changed

+21
-56
lines changed

2 files changed

+21
-56
lines changed

packages/core/src/utils/misc.ts

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import { snipLine } from './string';
77
import { GLOBAL_OBJ } from './worldwide';
88

99
interface CryptoInternal {
10-
getRandomValues(array: Uint8Array): Uint8Array;
1110
randomUUID?(): string;
1211
}
1312

@@ -22,37 +21,34 @@ function getCrypto(): CryptoInternal | undefined {
2221
return gbl.crypto || gbl.msCrypto;
2322
}
2423

24+
let emptyUuid: string | undefined;
25+
26+
function getRandomByte(): number {
27+
return Math.random() * 16;
28+
}
29+
2530
/**
2631
* UUID4 generator
2732
* @param crypto Object that provides the crypto API.
2833
* @returns string Generated UUID4.
2934
*/
3035
export function uuid4(crypto = getCrypto()): string {
31-
let getRandomByte = (): number => Math.random() * 16;
3236
try {
3337
if (crypto?.randomUUID) {
3438
return crypto.randomUUID().replace(/-/g, '');
3539
}
36-
if (crypto?.getRandomValues) {
37-
getRandomByte = () => {
38-
// crypto.getRandomValues might return undefined instead of the typed array
39-
// in old Chromium versions (e.g. 23.0.1235.0 (151422))
40-
// However, `typedArray` is still filled in-place.
41-
// @see https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues#typedarray
42-
const typedArray = new Uint8Array(1);
43-
crypto.getRandomValues(typedArray);
44-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
45-
return typedArray[0]!;
46-
};
47-
}
4840
} catch {
4941
// some runtimes can crash invoking crypto
5042
// https://github.com/getsentry/sentry-javascript/issues/8935
5143
}
5244

53-
// http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/2117523#2117523
54-
// Concatenating the following numbers as strings results in '10000000100040008000100000000000'
55-
return (([1e7] as unknown as string) + 1e3 + 4e3 + 8e3 + 1e11).replace(/[018]/g, c =>
45+
if (!emptyUuid) {
46+
// http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/2117523#2117523
47+
// Concatenating the following numbers as strings results in '10000000100040008000100000000000'
48+
emptyUuid = ([1e7] as unknown as string) + 1e3 + 4e3 + 8e3 + 1e11;
49+
}
50+
51+
return emptyUuid.replace(/[018]/g, c =>
5652
// eslint-disable-next-line no-bitwise
5753
((c as unknown as number) ^ ((getRandomByte() & 15) >> ((c as unknown as number) / 4))).toString(16),
5854
);

packages/core/test/lib/utils/misc.test.ts

Lines changed: 8 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -292,36 +292,29 @@ describe('checkOrSetAlreadyCaught()', () => {
292292

293293
describe('uuid4 generation', () => {
294294
const uuid4Regex = /^[0-9A-F]{12}[4][0-9A-F]{3}[89AB][0-9A-F]{15}$/i;
295-
it('returns valid uuid v4 ids via Math.random', () => {
295+
it('returns valid and unique uuid v4 ids via Math.random', () => {
296+
const uuids = new Set<string>();
296297
for (let index = 0; index < 1_000; index++) {
297-
expect(uuid4()).toMatch(uuid4Regex);
298-
}
299-
});
300-
301-
it('returns valid uuid v4 ids via crypto.getRandomValues', () => {
302-
// eslint-disable-next-line @typescript-eslint/no-var-requires
303-
const cryptoMod = require('crypto');
304-
305-
const crypto = { getRandomValues: cryptoMod.getRandomValues };
306-
307-
for (let index = 0; index < 1_000; index++) {
308-
expect(uuid4(crypto)).toMatch(uuid4Regex);
298+
const id = uuid4();
299+
expect(id).toMatch(uuid4Regex);
300+
uuids.add(id);
309301
}
302+
expect(uuids.size).toBe(1_000);
310303
});
311304

312305
it('returns valid uuid v4 ids via crypto.randomUUID', () => {
313306
// eslint-disable-next-line @typescript-eslint/no-var-requires
314307
const cryptoMod = require('crypto');
315308

316-
const crypto = { getRandomValues: cryptoMod.getRandomValues, randomUUID: cryptoMod.randomUUID };
309+
const crypto = { randomUUID: cryptoMod.randomUUID };
317310

318311
for (let index = 0; index < 1_000; index++) {
319312
expect(uuid4(crypto)).toMatch(uuid4Regex);
320313
}
321314
});
322315

323316
it("return valid uuid v4 even if crypto doesn't exists", () => {
324-
const crypto = { getRandomValues: undefined, randomUUID: undefined };
317+
const crypto = { randomUUID: undefined };
325318

326319
for (let index = 0; index < 1_000; index++) {
327320
expect(uuid4(crypto)).toMatch(uuid4Regex);
@@ -330,9 +323,6 @@ describe('uuid4 generation', () => {
330323

331324
it('return valid uuid v4 even if crypto invoked causes an error', () => {
332325
const crypto = {
333-
getRandomValues: () => {
334-
throw new Error('yo');
335-
},
336326
randomUUID: () => {
337327
throw new Error('yo');
338328
},
@@ -342,25 +332,4 @@ describe('uuid4 generation', () => {
342332
expect(uuid4(crypto)).toMatch(uuid4Regex);
343333
}
344334
});
345-
346-
// Corner case related to crypto.getRandomValues being only
347-
// semi-implemented (e.g. Chromium 23.0.1235.0 (151422))
348-
it('returns valid uuid v4 even if crypto.getRandomValues does not return a typed array', () => {
349-
// eslint-disable-next-line @typescript-eslint/no-var-requires
350-
const cryptoMod = require('crypto');
351-
352-
const getRandomValues = (typedArray: Uint8Array) => {
353-
if (cryptoMod.getRandomValues) {
354-
cryptoMod.getRandomValues(typedArray);
355-
}
356-
};
357-
358-
const crypto = { getRandomValues };
359-
360-
for (let index = 0; index < 1_000; index++) {
361-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
362-
// @ts-ignore - we are testing a corner case
363-
expect(uuid4(crypto)).toMatch(uuid4Regex);
364-
}
365-
});
366335
});

0 commit comments

Comments
 (0)