From d853dad5ade4b9b274ad667b46ccf006e47dca13 Mon Sep 17 00:00:00 2001 From: Anthony Barone Date: Tue, 18 Nov 2025 20:37:21 +0000 Subject: [PATCH 1/7] Add support for sending session ID for telemetry --- packages/telemetry/src/api.test.ts | 119 ++++++++++++++++-- packages/telemetry/src/api.ts | 18 ++- packages/telemetry/src/constants.ts | 10 ++ .../src/logging/installation-id-provider.ts | 5 +- 4 files changed, 141 insertions(+), 11 deletions(-) diff --git a/packages/telemetry/src/api.test.ts b/packages/telemetry/src/api.test.ts index 62529a166e2..9da6a38f753 100644 --- a/packages/telemetry/src/api.test.ts +++ b/packages/telemetry/src/api.test.ts @@ -35,6 +35,7 @@ import { import { Component, ComponentType } from '@firebase/component'; import { FirebaseAppCheckInternal } from '@firebase/app-check-interop-types'; import { captureError, flush, getTelemetry } from './api'; +import { LOG_ENTRY_ATTRIBUTE_KEYS, TELEMETRY_SESSION_ID_KEY } from './constants'; import { TelemetryService } from './service'; import { registerTelemetry } from './register'; import { _FirebaseInstallationsInternal } from '@firebase/installations'; @@ -127,7 +128,7 @@ describe('Top level API', () => { expect(log.attributes).to.deep.equal({ 'error.type': 'TestError', 'error.stack': '...stack trace...', - 'app.version': 'unset' + [LOG_ENTRY_ATTRIBUTE_KEYS.APP_VERSION]: 'unset' }); }); @@ -144,7 +145,7 @@ describe('Top level API', () => { expect(log.attributes).to.deep.equal({ 'error.type': 'Error', 'error.stack': 'No stack trace available', - 'app.version': 'unset' + [LOG_ENTRY_ATTRIBUTE_KEYS.APP_VERSION]: 'unset' }); }); @@ -156,7 +157,7 @@ describe('Top level API', () => { expect(log.severityNumber).to.equal(SeverityNumber.ERROR); expect(log.body).to.equal('a string error'); expect(log.attributes).to.deep.equal({ - 'app.version': 'unset' + [LOG_ENTRY_ATTRIBUTE_KEYS.APP_VERSION]: 'unset' }); }); @@ -168,7 +169,7 @@ describe('Top level API', () => { expect(log.severityNumber).to.equal(SeverityNumber.ERROR); expect(log.body).to.equal('Unknown error type: number'); expect(log.attributes).to.deep.equal({ - 'app.version': 'unset' + [LOG_ENTRY_ATTRIBUTE_KEYS.APP_VERSION]: 'unset' }); }); @@ -195,7 +196,7 @@ describe('Top level API', () => { expect(emittedLogs[0].attributes).to.deep.equal({ 'error.type': 'TestError', 'error.stack': '...stack trace...', - 'app.version': 'unset', + [LOG_ENTRY_ATTRIBUTE_KEYS.APP_VERSION]: 'unset', 'logging.googleapis.com/trace': `projects/${PROJECT_ID}/traces/my-trace`, 'logging.googleapis.com/spanId': `my-span` }); @@ -220,7 +221,7 @@ describe('Top level API', () => { expect(log.attributes).to.deep.equal({ 'error.type': 'TestError', 'error.stack': '...stack trace...', - 'app.version': 'unset', + [LOG_ENTRY_ATTRIBUTE_KEYS.APP_VERSION]: 'unset', strAttr: 'string attribute', mapAttr: { boolAttr: true, @@ -244,7 +245,111 @@ describe('Top level API', () => { expect(emittedLogs.length).to.equal(1); const log = emittedLogs[0]; expect(log.attributes).to.deep.equal({ - 'app.version': '1.0.0' + [LOG_ENTRY_ATTRIBUTE_KEYS.APP_VERSION]: '1.0.0' + }); + }); + + describe('Session Metadata', () => { + let originalSessionStorage: Storage | undefined; + let originalCrypto: Crypto | undefined; + + beforeEach(() => { + // @ts-ignore + originalSessionStorage = global.sessionStorage; + // @ts-ignore + originalCrypto = global.crypto; + }); + + afterEach(() => { + Object.defineProperty(global, 'sessionStorage', { + value: originalSessionStorage, + writable: true + }); + Object.defineProperty(global, 'crypto', { + value: originalCrypto, + writable: true + }); + }); + + it('should generate and store a new session ID if none exists', () => { + const sessionStorageMock = { + getItem: () => null, + setItem: (_: string, __: string) => { } + }; + sessionStorageMock.setItem = ( + key: string, + value: string + ) => { + // @ts-ignore + sessionStorageMock[key] = value; + }; + const cryptoMock = { + randomUUID: () => 'new-session-id' + }; + + Object.defineProperty(global, 'sessionStorage', { + value: sessionStorageMock, + writable: true + }); + Object.defineProperty(global, 'crypto', { + value: cryptoMock, + writable: true + }); + + captureError(fakeTelemetry, 'error'); + + expect(emittedLogs.length).to.equal(1); + const log = emittedLogs[0]; + expect(log.attributes![LOG_ENTRY_ATTRIBUTE_KEYS.SESSION_ID]).to.equal('new-session-id'); + // @ts-ignore + expect(sessionStorageMock[TELEMETRY_SESSION_ID_KEY]).to.equal( + 'new-session-id' + ); + }); + + it('should retrieve existing session ID from sessionStorage', () => { + const sessionStorageMock = { + getItem: () => 'existing-session-id', + setItem: () => { } + }; + const cryptoMock = { + randomUUID: () => 'new-session-id' + }; + + Object.defineProperty(global, 'sessionStorage', { + value: sessionStorageMock, + writable: true + }); + Object.defineProperty(global, 'crypto', { + value: cryptoMock, + writable: true + }); + + captureError(fakeTelemetry, 'error'); + + expect(emittedLogs.length).to.equal(1); + const log = emittedLogs[0]; + expect(log.attributes![LOG_ENTRY_ATTRIBUTE_KEYS.SESSION_ID]).to.equal('existing-session-id'); + }); + + it('should handle errors when accessing sessionStorage', () => { + const sessionStorageMock = { + getItem: () => { + throw new Error('SecurityError'); + }, + setItem: () => { } + }; + + Object.defineProperty(global, 'sessionStorage', { + value: sessionStorageMock, + writable: true + }); + + captureError(fakeTelemetry, 'error'); + + expect(emittedLogs.length).to.equal(1); + const log = emittedLogs[0]; + expect(log.attributes![LOG_ENTRY_ATTRIBUTE_KEYS.SESSION_ID]).to.be.undefined; }); }); }); diff --git a/packages/telemetry/src/api.ts b/packages/telemetry/src/api.ts index 72b6c123e23..010ea596535 100644 --- a/packages/telemetry/src/api.ts +++ b/packages/telemetry/src/api.ts @@ -16,7 +16,7 @@ */ import { _getProvider, FirebaseApp, getApp } from '@firebase/app'; -import { TELEMETRY_TYPE } from './constants'; +import { LOG_ENTRY_ATTRIBUTE_KEYS, TELEMETRY_SESSION_ID_KEY, TELEMETRY_TYPE } from './constants'; import { Telemetry, TelemetryOptions } from './public-types'; import { Provider } from '@firebase/component'; import { AnyValueMap, SeverityNumber } from '@opentelemetry/api-logs'; @@ -98,7 +98,21 @@ export function captureError( if ((telemetry as TelemetryService).options?.appVersion) { appVersion = (telemetry as TelemetryService).options!.appVersion!; } - customAttributes['app.version'] = appVersion; + customAttributes[LOG_ENTRY_ATTRIBUTE_KEYS.APP_VERSION] = appVersion; + + // Add session ID metadata + if (typeof sessionStorage !== 'undefined') { + try { + let sessionId = sessionStorage.getItem(TELEMETRY_SESSION_ID_KEY); + if (!sessionId) { + sessionId = crypto.randomUUID(); + sessionStorage.setItem(TELEMETRY_SESSION_ID_KEY, sessionId); + } + customAttributes[LOG_ENTRY_ATTRIBUTE_KEYS.SESSION_ID] = sessionId; + } catch (e) { + // Ignore errors accessing sessionStorage (e.g. security restrictions) + } + } if (error instanceof Error) { logger.emit({ diff --git a/packages/telemetry/src/constants.ts b/packages/telemetry/src/constants.ts index 5928220b811..6888673369b 100644 --- a/packages/telemetry/src/constants.ts +++ b/packages/telemetry/src/constants.ts @@ -17,3 +17,13 @@ /** Type constant for Firebase Telemetry. */ export const TELEMETRY_TYPE = 'telemetry'; + +/** Key for storing the session ID in sessionStorage. */ +export const TELEMETRY_SESSION_ID_KEY = 'firebasetelemetry.sessionid'; + +/** Keys for attributes in log entries. */ +export const LOG_ENTRY_ATTRIBUTE_KEYS = { + USER_ID: 'user.id', + SESSION_ID: 'session.id', + APP_VERSION: 'app.version', +}; diff --git a/packages/telemetry/src/logging/installation-id-provider.ts b/packages/telemetry/src/logging/installation-id-provider.ts index 3e507a33856..6d68d688482 100644 --- a/packages/telemetry/src/logging/installation-id-provider.ts +++ b/packages/telemetry/src/logging/installation-id-provider.ts @@ -18,6 +18,7 @@ import { Provider } from '@firebase/component'; import { DynamicLogAttributeProvider, LogEntryAttribute } from '../types'; import { _FirebaseInstallationsInternal } from '@firebase/installations'; +import { LOG_ENTRY_ATTRIBUTE_KEYS } from '../constants'; /** * Allows logging to include the client's installation ID. @@ -45,7 +46,7 @@ export class InstallationIdProvider implements DynamicLogAttributeProvider { return null; } if (this._iid) { - return ['user.id', this._iid]; + return [LOG_ENTRY_ATTRIBUTE_KEYS.USER_ID, this._iid]; } const iid = await this.installations.getId(); @@ -54,6 +55,6 @@ export class InstallationIdProvider implements DynamicLogAttributeProvider { } this._iid = iid; - return ['user.id', iid]; + return [LOG_ENTRY_ATTRIBUTE_KEYS.USER_ID, iid]; } } From 0db1daa8ded0e8d39bc8c394ad662e425e8b8368 Mon Sep 17 00:00:00 2001 From: Anthony Barone Date: Wed, 19 Nov 2025 15:46:41 +0000 Subject: [PATCH 2/7] Simplify test --- packages/telemetry/src/api.test.ts | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/packages/telemetry/src/api.test.ts b/packages/telemetry/src/api.test.ts index 9da6a38f753..ed5f6ee0fb7 100644 --- a/packages/telemetry/src/api.test.ts +++ b/packages/telemetry/src/api.test.ts @@ -272,16 +272,12 @@ describe('Top level API', () => { }); it('should generate and store a new session ID if none exists', () => { + const storage: Record = {}; const sessionStorageMock = { - getItem: () => null, - setItem: (_: string, __: string) => { } - }; - sessionStorageMock.setItem = ( - key: string, - value: string - ) => { - // @ts-ignore - sessionStorageMock[key] = value; + getItem: (key: string) => storage[key] || null, + setItem: (key: string, value: string) => { + storage[key] = value; + } }; const cryptoMock = { randomUUID: () => 'new-session-id' @@ -301,8 +297,7 @@ describe('Top level API', () => { expect(emittedLogs.length).to.equal(1); const log = emittedLogs[0]; expect(log.attributes![LOG_ENTRY_ATTRIBUTE_KEYS.SESSION_ID]).to.equal('new-session-id'); - // @ts-ignore - expect(sessionStorageMock[TELEMETRY_SESSION_ID_KEY]).to.equal( + expect(storage[TELEMETRY_SESSION_ID_KEY]).to.equal( 'new-session-id' ); }); From 519e9da69c4ab044c0d303824de3a678d97bba4a Mon Sep 17 00:00:00 2001 From: Anthony Barone Date: Wed, 19 Nov 2025 10:47:32 -0500 Subject: [PATCH 3/7] Check for existence of crypto.randomUUID Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- packages/telemetry/src/api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/telemetry/src/api.ts b/packages/telemetry/src/api.ts index 010ea596535..f0a8c8dc379 100644 --- a/packages/telemetry/src/api.ts +++ b/packages/telemetry/src/api.ts @@ -101,7 +101,7 @@ export function captureError( customAttributes[LOG_ENTRY_ATTRIBUTE_KEYS.APP_VERSION] = appVersion; // Add session ID metadata - if (typeof sessionStorage !== 'undefined') { + if (typeof sessionStorage !== 'undefined' && typeof crypto?.randomUUID === 'function') { try { let sessionId = sessionStorage.getItem(TELEMETRY_SESSION_ID_KEY); if (!sessionId) { From 638f89090a403c53a15cfa566d546e41dbcd58df Mon Sep 17 00:00:00 2001 From: Anthony Barone Date: Wed, 19 Nov 2025 15:48:05 +0000 Subject: [PATCH 4/7] Format --- packages/telemetry/src/api.test.ts | 20 +++++++++++++------- packages/telemetry/src/api.ts | 11 +++++++++-- packages/telemetry/src/constants.ts | 2 +- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/packages/telemetry/src/api.test.ts b/packages/telemetry/src/api.test.ts index ed5f6ee0fb7..01a2f33d3ba 100644 --- a/packages/telemetry/src/api.test.ts +++ b/packages/telemetry/src/api.test.ts @@ -35,7 +35,10 @@ import { import { Component, ComponentType } from '@firebase/component'; import { FirebaseAppCheckInternal } from '@firebase/app-check-interop-types'; import { captureError, flush, getTelemetry } from './api'; -import { LOG_ENTRY_ATTRIBUTE_KEYS, TELEMETRY_SESSION_ID_KEY } from './constants'; +import { + LOG_ENTRY_ATTRIBUTE_KEYS, + TELEMETRY_SESSION_ID_KEY +} from './constants'; import { TelemetryService } from './service'; import { registerTelemetry } from './register'; import { _FirebaseInstallationsInternal } from '@firebase/installations'; @@ -296,16 +299,16 @@ describe('Top level API', () => { expect(emittedLogs.length).to.equal(1); const log = emittedLogs[0]; - expect(log.attributes![LOG_ENTRY_ATTRIBUTE_KEYS.SESSION_ID]).to.equal('new-session-id'); - expect(storage[TELEMETRY_SESSION_ID_KEY]).to.equal( + expect(log.attributes![LOG_ENTRY_ATTRIBUTE_KEYS.SESSION_ID]).to.equal( 'new-session-id' ); + expect(storage[TELEMETRY_SESSION_ID_KEY]).to.equal('new-session-id'); }); it('should retrieve existing session ID from sessionStorage', () => { const sessionStorageMock = { getItem: () => 'existing-session-id', - setItem: () => { } + setItem: () => {} }; const cryptoMock = { randomUUID: () => 'new-session-id' @@ -324,7 +327,9 @@ describe('Top level API', () => { expect(emittedLogs.length).to.equal(1); const log = emittedLogs[0]; - expect(log.attributes![LOG_ENTRY_ATTRIBUTE_KEYS.SESSION_ID]).to.equal('existing-session-id'); + expect(log.attributes![LOG_ENTRY_ATTRIBUTE_KEYS.SESSION_ID]).to.equal( + 'existing-session-id' + ); }); it('should handle errors when accessing sessionStorage', () => { @@ -332,7 +337,7 @@ describe('Top level API', () => { getItem: () => { throw new Error('SecurityError'); }, - setItem: () => { } + setItem: () => {} }; Object.defineProperty(global, 'sessionStorage', { @@ -344,7 +349,8 @@ describe('Top level API', () => { expect(emittedLogs.length).to.equal(1); const log = emittedLogs[0]; - expect(log.attributes![LOG_ENTRY_ATTRIBUTE_KEYS.SESSION_ID]).to.be.undefined; + expect(log.attributes![LOG_ENTRY_ATTRIBUTE_KEYS.SESSION_ID]).to.be + .undefined; }); }); }); diff --git a/packages/telemetry/src/api.ts b/packages/telemetry/src/api.ts index f0a8c8dc379..f1f16896d8b 100644 --- a/packages/telemetry/src/api.ts +++ b/packages/telemetry/src/api.ts @@ -16,7 +16,11 @@ */ import { _getProvider, FirebaseApp, getApp } from '@firebase/app'; -import { LOG_ENTRY_ATTRIBUTE_KEYS, TELEMETRY_SESSION_ID_KEY, TELEMETRY_TYPE } from './constants'; +import { + LOG_ENTRY_ATTRIBUTE_KEYS, + TELEMETRY_SESSION_ID_KEY, + TELEMETRY_TYPE +} from './constants'; import { Telemetry, TelemetryOptions } from './public-types'; import { Provider } from '@firebase/component'; import { AnyValueMap, SeverityNumber } from '@opentelemetry/api-logs'; @@ -101,7 +105,10 @@ export function captureError( customAttributes[LOG_ENTRY_ATTRIBUTE_KEYS.APP_VERSION] = appVersion; // Add session ID metadata - if (typeof sessionStorage !== 'undefined' && typeof crypto?.randomUUID === 'function') { + if ( + typeof sessionStorage !== 'undefined' && + typeof crypto?.randomUUID === 'function' + ) { try { let sessionId = sessionStorage.getItem(TELEMETRY_SESSION_ID_KEY); if (!sessionId) { diff --git a/packages/telemetry/src/constants.ts b/packages/telemetry/src/constants.ts index 6888673369b..52ce36d8db6 100644 --- a/packages/telemetry/src/constants.ts +++ b/packages/telemetry/src/constants.ts @@ -25,5 +25,5 @@ export const TELEMETRY_SESSION_ID_KEY = 'firebasetelemetry.sessionid'; export const LOG_ENTRY_ATTRIBUTE_KEYS = { USER_ID: 'user.id', SESSION_ID: 'session.id', - APP_VERSION: 'app.version', + APP_VERSION: 'app.version' }; From 22fd55f2975927cfe6bd22b10e6bd2fafe37d7a7 Mon Sep 17 00:00:00 2001 From: Anthony Barone Date: Wed, 19 Nov 2025 16:30:56 +0000 Subject: [PATCH 5/7] Refactor and cleanup api test --- packages/telemetry/src/api.test.ts | 122 +++++++++++++---------------- 1 file changed, 55 insertions(+), 67 deletions(-) diff --git a/packages/telemetry/src/api.test.ts b/packages/telemetry/src/api.test.ts index 01a2f33d3ba..555c4fe94a2 100644 --- a/packages/telemetry/src/api.test.ts +++ b/packages/telemetry/src/api.test.ts @@ -46,6 +46,7 @@ import { _FirebaseInstallationsInternal } from '@firebase/installations'; const PROJECT_ID = 'my-project'; const APP_ID = 'my-appid'; const API_KEY = 'my-api-key'; +const MOCK_SESSION_ID = '00000000-0000-0000-0000-000000000000'; const emittedLogs: LogRecord[] = []; @@ -78,15 +79,51 @@ const fakeTelemetry: Telemetry = { describe('Top level API', () => { let app: FirebaseApp; + let originalSessionStorage: Storage | undefined; + let originalCrypto: Crypto | undefined; + let storage: Record = {}; beforeEach(() => { // Clear the logs before each test. emittedLogs.length = 0; app = getFakeApp(); + storage = {}; + + // @ts-ignore + originalSessionStorage = global.sessionStorage; + // @ts-ignore + originalCrypto = global.crypto; + + const sessionStorageMock: Partial = { + getItem: (key: string) => storage[key] || null, + setItem: (key: string, value: string) => { + storage[key] = value; + } + }; + const cryptoMock: Partial = { + randomUUID: () => MOCK_SESSION_ID + }; + + Object.defineProperty(global, 'sessionStorage', { + value: sessionStorageMock, + writable: true + }); + Object.defineProperty(global, 'crypto', { + value: cryptoMock, + writable: true + }); }); afterEach(async () => { await deleteApp(app); + Object.defineProperty(global, 'sessionStorage', { + value: originalSessionStorage, + writable: true + }); + Object.defineProperty(global, 'crypto', { + value: originalCrypto, + writable: true + }); }); describe('getTelemetry()', () => { @@ -131,7 +168,8 @@ describe('Top level API', () => { expect(log.attributes).to.deep.equal({ 'error.type': 'TestError', 'error.stack': '...stack trace...', - [LOG_ENTRY_ATTRIBUTE_KEYS.APP_VERSION]: 'unset' + [LOG_ENTRY_ATTRIBUTE_KEYS.APP_VERSION]: 'unset', + [LOG_ENTRY_ATTRIBUTE_KEYS.SESSION_ID]: MOCK_SESSION_ID }); }); @@ -148,7 +186,8 @@ describe('Top level API', () => { expect(log.attributes).to.deep.equal({ 'error.type': 'Error', 'error.stack': 'No stack trace available', - [LOG_ENTRY_ATTRIBUTE_KEYS.APP_VERSION]: 'unset' + [LOG_ENTRY_ATTRIBUTE_KEYS.APP_VERSION]: 'unset', + [LOG_ENTRY_ATTRIBUTE_KEYS.SESSION_ID]: MOCK_SESSION_ID }); }); @@ -160,7 +199,8 @@ describe('Top level API', () => { expect(log.severityNumber).to.equal(SeverityNumber.ERROR); expect(log.body).to.equal('a string error'); expect(log.attributes).to.deep.equal({ - [LOG_ENTRY_ATTRIBUTE_KEYS.APP_VERSION]: 'unset' + [LOG_ENTRY_ATTRIBUTE_KEYS.APP_VERSION]: 'unset', + [LOG_ENTRY_ATTRIBUTE_KEYS.SESSION_ID]: MOCK_SESSION_ID }); }); @@ -172,7 +212,8 @@ describe('Top level API', () => { expect(log.severityNumber).to.equal(SeverityNumber.ERROR); expect(log.body).to.equal('Unknown error type: number'); expect(log.attributes).to.deep.equal({ - [LOG_ENTRY_ATTRIBUTE_KEYS.APP_VERSION]: 'unset' + [LOG_ENTRY_ATTRIBUTE_KEYS.APP_VERSION]: 'unset', + [LOG_ENTRY_ATTRIBUTE_KEYS.SESSION_ID]: MOCK_SESSION_ID }); }); @@ -201,7 +242,8 @@ describe('Top level API', () => { 'error.stack': '...stack trace...', [LOG_ENTRY_ATTRIBUTE_KEYS.APP_VERSION]: 'unset', 'logging.googleapis.com/trace': `projects/${PROJECT_ID}/traces/my-trace`, - 'logging.googleapis.com/spanId': `my-span` + 'logging.googleapis.com/spanId': `my-span`, + [LOG_ENTRY_ATTRIBUTE_KEYS.SESSION_ID]: MOCK_SESSION_ID }); }); @@ -230,7 +272,8 @@ describe('Top level API', () => { boolAttr: true, numAttr: 2 }, - arrAttr: [1, 2, 3] + arrAttr: [1, 2, 3], + [LOG_ENTRY_ATTRIBUTE_KEYS.SESSION_ID]: MOCK_SESSION_ID }); }); @@ -248,80 +291,25 @@ describe('Top level API', () => { expect(emittedLogs.length).to.equal(1); const log = emittedLogs[0]; expect(log.attributes).to.deep.equal({ - [LOG_ENTRY_ATTRIBUTE_KEYS.APP_VERSION]: '1.0.0' + [LOG_ENTRY_ATTRIBUTE_KEYS.APP_VERSION]: '1.0.0', + [LOG_ENTRY_ATTRIBUTE_KEYS.SESSION_ID]: MOCK_SESSION_ID }); }); describe('Session Metadata', () => { - let originalSessionStorage: Storage | undefined; - let originalCrypto: Crypto | undefined; - - beforeEach(() => { - // @ts-ignore - originalSessionStorage = global.sessionStorage; - // @ts-ignore - originalCrypto = global.crypto; - }); - - afterEach(() => { - Object.defineProperty(global, 'sessionStorage', { - value: originalSessionStorage, - writable: true - }); - Object.defineProperty(global, 'crypto', { - value: originalCrypto, - writable: true - }); - }); - it('should generate and store a new session ID if none exists', () => { - const storage: Record = {}; - const sessionStorageMock = { - getItem: (key: string) => storage[key] || null, - setItem: (key: string, value: string) => { - storage[key] = value; - } - }; - const cryptoMock = { - randomUUID: () => 'new-session-id' - }; - - Object.defineProperty(global, 'sessionStorage', { - value: sessionStorageMock, - writable: true - }); - Object.defineProperty(global, 'crypto', { - value: cryptoMock, - writable: true - }); - captureError(fakeTelemetry, 'error'); expect(emittedLogs.length).to.equal(1); const log = emittedLogs[0]; expect(log.attributes![LOG_ENTRY_ATTRIBUTE_KEYS.SESSION_ID]).to.equal( - 'new-session-id' + MOCK_SESSION_ID ); - expect(storage[TELEMETRY_SESSION_ID_KEY]).to.equal('new-session-id'); + expect(storage[TELEMETRY_SESSION_ID_KEY]).to.equal(MOCK_SESSION_ID); }); it('should retrieve existing session ID from sessionStorage', () => { - const sessionStorageMock = { - getItem: () => 'existing-session-id', - setItem: () => {} - }; - const cryptoMock = { - randomUUID: () => 'new-session-id' - }; - - Object.defineProperty(global, 'sessionStorage', { - value: sessionStorageMock, - writable: true - }); - Object.defineProperty(global, 'crypto', { - value: cryptoMock, - writable: true - }); + storage[TELEMETRY_SESSION_ID_KEY] = 'existing-session-id'; captureError(fakeTelemetry, 'error'); @@ -333,7 +321,7 @@ describe('Top level API', () => { }); it('should handle errors when accessing sessionStorage', () => { - const sessionStorageMock = { + const sessionStorageMock: Partial = { getItem: () => { throw new Error('SecurityError'); }, From c0d33c09b39e6ebeade8b578a6d36953543d144d Mon Sep 17 00:00:00 2001 From: Anthony Barone Date: Wed, 19 Nov 2025 16:34:16 +0000 Subject: [PATCH 6/7] Add test --- packages/telemetry/src/api.test.ts | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/packages/telemetry/src/api.test.ts b/packages/telemetry/src/api.test.ts index 555c4fe94a2..66a16bf352b 100644 --- a/packages/telemetry/src/api.test.ts +++ b/packages/telemetry/src/api.test.ts @@ -320,7 +320,7 @@ describe('Top level API', () => { ); }); - it('should handle errors when accessing sessionStorage', () => { + it('should handle errors when sessionStorage.getItem throws', () => { const sessionStorageMock: Partial = { getItem: () => { throw new Error('SecurityError'); @@ -340,6 +340,26 @@ describe('Top level API', () => { expect(log.attributes![LOG_ENTRY_ATTRIBUTE_KEYS.SESSION_ID]).to.be .undefined; }); + + it('should handle errors when sessionStorage.setItem throws', () => { + const sessionStorageMock: Partial = { + getItem: () => null, // Emulate no existing session ID + setItem: () => { + throw new Error('SecurityError'); + } + }; + + Object.defineProperty(global, 'sessionStorage', { + value: sessionStorageMock, + writable: true + }); + + captureError(fakeTelemetry, 'error'); + + expect(emittedLogs.length).to.equal(1); + const log = emittedLogs[0]; + expect(log.attributes![LOG_ENTRY_ATTRIBUTE_KEYS.SESSION_ID]).to.be.undefined; + }); }); }); From 33e70e660c6cd2b20d7e2403ac9ed409a5af9ce3 Mon Sep 17 00:00:00 2001 From: Anthony Barone Date: Wed, 19 Nov 2025 16:37:48 +0000 Subject: [PATCH 7/7] Formatting --- packages/telemetry/src/api.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/telemetry/src/api.test.ts b/packages/telemetry/src/api.test.ts index 66a16bf352b..b954987b696 100644 --- a/packages/telemetry/src/api.test.ts +++ b/packages/telemetry/src/api.test.ts @@ -358,7 +358,8 @@ describe('Top level API', () => { expect(emittedLogs.length).to.equal(1); const log = emittedLogs[0]; - expect(log.attributes![LOG_ENTRY_ATTRIBUTE_KEYS.SESSION_ID]).to.be.undefined; + expect(log.attributes![LOG_ENTRY_ATTRIBUTE_KEYS.SESSION_ID]).to.be + .undefined; }); }); });