From 42ece80b544bd9ed27d00892dadd538fa8c7052f Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Wed, 15 Oct 2025 12:27:09 +0200 Subject: [PATCH 1/7] fix(core): Improve uuid performance --- packages/core/src/utils/misc.ts | 30 +++++++-------- packages/core/test/lib/utils/misc.test.ts | 47 ++++------------------- 2 files changed, 21 insertions(+), 56 deletions(-) diff --git a/packages/core/src/utils/misc.ts b/packages/core/src/utils/misc.ts index 607eff129fe5..69cd217345b8 100644 --- a/packages/core/src/utils/misc.ts +++ b/packages/core/src/utils/misc.ts @@ -7,7 +7,6 @@ import { snipLine } from './string'; import { GLOBAL_OBJ } from './worldwide'; interface CryptoInternal { - getRandomValues(array: Uint8Array): Uint8Array; randomUUID?(): string; } @@ -22,37 +21,34 @@ function getCrypto(): CryptoInternal | undefined { return gbl.crypto || gbl.msCrypto; } +let emptyUuid: string | undefined; + +function getRandomByte(): number { + return Math.random() * 16; +} + /** * UUID4 generator * @param crypto Object that provides the crypto API. * @returns string Generated UUID4. */ export function uuid4(crypto = getCrypto()): string { - let getRandomByte = (): number => Math.random() * 16; try { if (crypto?.randomUUID) { return crypto.randomUUID().replace(/-/g, ''); } - if (crypto?.getRandomValues) { - getRandomByte = () => { - // crypto.getRandomValues might return undefined instead of the typed array - // in old Chromium versions (e.g. 23.0.1235.0 (151422)) - // However, `typedArray` is still filled in-place. - // @see https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues#typedarray - const typedArray = new Uint8Array(1); - crypto.getRandomValues(typedArray); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - return typedArray[0]!; - }; - } } catch { // some runtimes can crash invoking crypto // https://github.com/getsentry/sentry-javascript/issues/8935 } - // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/2117523#2117523 - // Concatenating the following numbers as strings results in '10000000100040008000100000000000' - return (([1e7] as unknown as string) + 1e3 + 4e3 + 8e3 + 1e11).replace(/[018]/g, c => + if (!emptyUuid) { + // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/2117523#2117523 + // Concatenating the following numbers as strings results in '10000000100040008000100000000000' + emptyUuid = ([1e7] as unknown as string) + 1e3 + 4e3 + 8e3 + 1e11; + } + + return emptyUuid.replace(/[018]/g, c => // eslint-disable-next-line no-bitwise ((c as unknown as number) ^ ((getRandomByte() & 15) >> ((c as unknown as number) / 4))).toString(16), ); diff --git a/packages/core/test/lib/utils/misc.test.ts b/packages/core/test/lib/utils/misc.test.ts index 83e7f4c05b66..885e2dc64b8d 100644 --- a/packages/core/test/lib/utils/misc.test.ts +++ b/packages/core/test/lib/utils/misc.test.ts @@ -292,28 +292,21 @@ describe('checkOrSetAlreadyCaught()', () => { describe('uuid4 generation', () => { const uuid4Regex = /^[0-9A-F]{12}[4][0-9A-F]{3}[89AB][0-9A-F]{15}$/i; - it('returns valid uuid v4 ids via Math.random', () => { + it('returns valid and unique uuid v4 ids via Math.random', () => { + const uuids = new Set(); for (let index = 0; index < 1_000; index++) { - expect(uuid4()).toMatch(uuid4Regex); - } - }); - - it('returns valid uuid v4 ids via crypto.getRandomValues', () => { - // eslint-disable-next-line @typescript-eslint/no-var-requires - const cryptoMod = require('crypto'); - - const crypto = { getRandomValues: cryptoMod.getRandomValues }; - - for (let index = 0; index < 1_000; index++) { - expect(uuid4(crypto)).toMatch(uuid4Regex); + const id = uuid4(); + expect(id).toMatch(uuid4Regex); + uuids.add(id); } + expect(uuids.size).toBe(1_000); }); it('returns valid uuid v4 ids via crypto.randomUUID', () => { // eslint-disable-next-line @typescript-eslint/no-var-requires const cryptoMod = require('crypto'); - const crypto = { getRandomValues: cryptoMod.getRandomValues, randomUUID: cryptoMod.randomUUID }; + const crypto = { randomUUID: cryptoMod.randomUUID }; for (let index = 0; index < 1_000; index++) { expect(uuid4(crypto)).toMatch(uuid4Regex); @@ -321,7 +314,7 @@ describe('uuid4 generation', () => { }); it("return valid uuid v4 even if crypto doesn't exists", () => { - const crypto = { getRandomValues: undefined, randomUUID: undefined }; + const crypto = { randomUUID: undefined }; for (let index = 0; index < 1_000; index++) { expect(uuid4(crypto)).toMatch(uuid4Regex); @@ -330,9 +323,6 @@ describe('uuid4 generation', () => { it('return valid uuid v4 even if crypto invoked causes an error', () => { const crypto = { - getRandomValues: () => { - throw new Error('yo'); - }, randomUUID: () => { throw new Error('yo'); }, @@ -342,25 +332,4 @@ describe('uuid4 generation', () => { expect(uuid4(crypto)).toMatch(uuid4Regex); } }); - - // Corner case related to crypto.getRandomValues being only - // semi-implemented (e.g. Chromium 23.0.1235.0 (151422)) - it('returns valid uuid v4 even if crypto.getRandomValues does not return a typed array', () => { - // eslint-disable-next-line @typescript-eslint/no-var-requires - const cryptoMod = require('crypto'); - - const getRandomValues = (typedArray: Uint8Array) => { - if (cryptoMod.getRandomValues) { - cryptoMod.getRandomValues(typedArray); - } - }; - - const crypto = { getRandomValues }; - - for (let index = 0; index < 1_000; index++) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - we are testing a corner case - expect(uuid4(crypto)).toMatch(uuid4Regex); - } - }); }); From f419fab0fff172d8dd42f2e1460149aa158cab92 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Wed, 15 Oct 2025 13:50:15 +0200 Subject: [PATCH 2/7] Fix tests --- .../trace-lifetime/startNewTraceSampling/init.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTraceSampling/init.js b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTraceSampling/init.js index 09af5f3e4ab4..0780d35cdb85 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTraceSampling/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTraceSampling/init.js @@ -2,9 +2,22 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; +const originalRandom = Math.random; + // Force this so that the initial sampleRand is consistent Math.random = () => 0.45; +// polyfill for crypto.randomUUID if not available (e.g. in non-secure contexts) +window.crypto = window.crypto || {}; +window.crypto.randomUUID = + window.crypto.randomUUID || + (() => { + return '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, c => + // eslint-disable-next-line no-bitwise + (c ^ (((originalRandom() * 16) & 15) >> (c / 4))).toString(16), + ); + }); + Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [Sentry.browserTracingIntegration()], From 850d921bd5bfe128ff9be8734007883e8248a9b1 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Wed, 15 Oct 2025 17:18:51 +0200 Subject: [PATCH 3/7] Fix test --- .../tracing/trace-lifetime/startNewTraceSampling/init.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTraceSampling/init.js b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTraceSampling/init.js index 0780d35cdb85..728aa274c2e8 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTraceSampling/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTraceSampling/init.js @@ -4,8 +4,8 @@ window.Sentry = Sentry; const originalRandom = Math.random; -// Force this so that the initial sampleRand is consistent -Math.random = () => 0.45; +// Force this so that the initial sampleRand is between 0.35 and 0.45 +Math.random = () => 0.35 + Math.random() * 0.1; // polyfill for crypto.randomUUID if not available (e.g. in non-secure contexts) window.crypto = window.crypto || {}; From c9995b992c666dfada86a5ea19cbf2af9df24944 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Wed, 15 Oct 2025 17:19:58 +0200 Subject: [PATCH 4/7] Fix test --- .../trace-lifetime/startNewTraceSampling/init.js | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTraceSampling/init.js b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTraceSampling/init.js index 728aa274c2e8..d73a9689312d 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTraceSampling/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTraceSampling/init.js @@ -2,22 +2,9 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; -const originalRandom = Math.random; - // Force this so that the initial sampleRand is between 0.35 and 0.45 Math.random = () => 0.35 + Math.random() * 0.1; -// polyfill for crypto.randomUUID if not available (e.g. in non-secure contexts) -window.crypto = window.crypto || {}; -window.crypto.randomUUID = - window.crypto.randomUUID || - (() => { - return '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, c => - // eslint-disable-next-line no-bitwise - (c ^ (((originalRandom() * 16) & 15) >> (c / 4))).toString(16), - ); - }); - Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [Sentry.browserTracingIntegration()], From b30322a4fb069421227486f3b921168935f27e47 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Thu, 16 Oct 2025 13:50:12 +0200 Subject: [PATCH 5/7] revert test --- .../tracing/trace-lifetime/startNewTraceSampling/init.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTraceSampling/init.js b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTraceSampling/init.js index d73a9689312d..09af5f3e4ab4 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTraceSampling/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTraceSampling/init.js @@ -2,8 +2,8 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; -// Force this so that the initial sampleRand is between 0.35 and 0.45 -Math.random = () => 0.35 + Math.random() * 0.1; +// Force this so that the initial sampleRand is consistent +Math.random = () => 0.45; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', From b58687a5b679ef2edc4de2de07d8ee33e456b11c Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Fri, 17 Oct 2025 14:44:31 +0200 Subject: [PATCH 6/7] Try and polyfill crypto.randomUUID --- .../tracing/trace-lifetime/startNewTraceSampling/init.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTraceSampling/init.js b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTraceSampling/init.js index 09af5f3e4ab4..af4262421ea4 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTraceSampling/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTraceSampling/init.js @@ -5,6 +5,15 @@ window.Sentry = Sentry; // Force this so that the initial sampleRand is consistent Math.random = () => 0.45; +// Polyfill crypto.randomUUID +crypto.randomUUID = function randomUUID() { + return ( + [1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, + // eslint-disable-next-line no-bitwise + c => (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16) + ); +}; + Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [Sentry.browserTracingIntegration()], From 7acf3b711b43708a1df101f171565f52b5ca361f Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Fri, 17 Oct 2025 14:50:14 +0200 Subject: [PATCH 7/7] Lint --- .../tracing/trace-lifetime/startNewTraceSampling/init.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTraceSampling/init.js b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTraceSampling/init.js index af4262421ea4..d32f77f4cb6b 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTraceSampling/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTraceSampling/init.js @@ -7,10 +7,10 @@ Math.random = () => 0.45; // Polyfill crypto.randomUUID crypto.randomUUID = function randomUUID() { - return ( - [1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, + return ([1e7] + 1e3 + 4e3 + 8e3 + 1e11).replace( + /[018]/g, // eslint-disable-next-line no-bitwise - c => (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16) + c => (c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16), ); };