@@ -35,13 +35,18 @@ import {
3535import { Component , ComponentType } from '@firebase/component' ;
3636import { FirebaseAppCheckInternal } from '@firebase/app-check-interop-types' ;
3737import { captureError , flush , getTelemetry } from './api' ;
38+ import {
39+ LOG_ENTRY_ATTRIBUTE_KEYS ,
40+ TELEMETRY_SESSION_ID_KEY
41+ } from './constants' ;
3842import { TelemetryService } from './service' ;
3943import { registerTelemetry } from './register' ;
4044import { _FirebaseInstallationsInternal } from '@firebase/installations' ;
4145
4246const PROJECT_ID = 'my-project' ;
4347const APP_ID = 'my-appid' ;
4448const API_KEY = 'my-api-key' ;
49+ const MOCK_SESSION_ID = '00000000-0000-0000-0000-000000000000' ;
4550
4651const emittedLogs : LogRecord [ ] = [ ] ;
4752
@@ -74,15 +79,51 @@ const fakeTelemetry: Telemetry = {
7479
7580describe ( 'Top level API' , ( ) => {
7681 let app : FirebaseApp ;
82+ let originalSessionStorage : Storage | undefined ;
83+ let originalCrypto : Crypto | undefined ;
84+ let storage : Record < string , string > = { } ;
7785
7886 beforeEach ( ( ) => {
7987 // Clear the logs before each test.
8088 emittedLogs . length = 0 ;
8189 app = getFakeApp ( ) ;
90+ storage = { } ;
91+
92+ // @ts -ignore
93+ originalSessionStorage = global . sessionStorage ;
94+ // @ts -ignore
95+ originalCrypto = global . crypto ;
96+
97+ const sessionStorageMock : Partial < Storage > = {
98+ getItem : ( key : string ) => storage [ key ] || null ,
99+ setItem : ( key : string , value : string ) => {
100+ storage [ key ] = value ;
101+ }
102+ } ;
103+ const cryptoMock : Partial < Crypto > = {
104+ randomUUID : ( ) => MOCK_SESSION_ID
105+ } ;
106+
107+ Object . defineProperty ( global , 'sessionStorage' , {
108+ value : sessionStorageMock ,
109+ writable : true
110+ } ) ;
111+ Object . defineProperty ( global , 'crypto' , {
112+ value : cryptoMock ,
113+ writable : true
114+ } ) ;
82115 } ) ;
83116
84117 afterEach ( async ( ) => {
85118 await deleteApp ( app ) ;
119+ Object . defineProperty ( global , 'sessionStorage' , {
120+ value : originalSessionStorage ,
121+ writable : true
122+ } ) ;
123+ Object . defineProperty ( global , 'crypto' , {
124+ value : originalCrypto ,
125+ writable : true
126+ } ) ;
86127 } ) ;
87128
88129 describe ( 'getTelemetry()' , ( ) => {
@@ -127,7 +168,8 @@ describe('Top level API', () => {
127168 expect ( log . attributes ) . to . deep . equal ( {
128169 'error.type' : 'TestError' ,
129170 'error.stack' : '...stack trace...' ,
130- 'app.version' : 'unset'
171+ [ LOG_ENTRY_ATTRIBUTE_KEYS . APP_VERSION ] : 'unset' ,
172+ [ LOG_ENTRY_ATTRIBUTE_KEYS . SESSION_ID ] : MOCK_SESSION_ID
131173 } ) ;
132174 } ) ;
133175
@@ -144,7 +186,8 @@ describe('Top level API', () => {
144186 expect ( log . attributes ) . to . deep . equal ( {
145187 'error.type' : 'Error' ,
146188 'error.stack' : 'No stack trace available' ,
147- 'app.version' : 'unset'
189+ [ LOG_ENTRY_ATTRIBUTE_KEYS . APP_VERSION ] : 'unset' ,
190+ [ LOG_ENTRY_ATTRIBUTE_KEYS . SESSION_ID ] : MOCK_SESSION_ID
148191 } ) ;
149192 } ) ;
150193
@@ -156,7 +199,8 @@ describe('Top level API', () => {
156199 expect ( log . severityNumber ) . to . equal ( SeverityNumber . ERROR ) ;
157200 expect ( log . body ) . to . equal ( 'a string error' ) ;
158201 expect ( log . attributes ) . to . deep . equal ( {
159- 'app.version' : 'unset'
202+ [ LOG_ENTRY_ATTRIBUTE_KEYS . APP_VERSION ] : 'unset' ,
203+ [ LOG_ENTRY_ATTRIBUTE_KEYS . SESSION_ID ] : MOCK_SESSION_ID
160204 } ) ;
161205 } ) ;
162206
@@ -168,7 +212,8 @@ describe('Top level API', () => {
168212 expect ( log . severityNumber ) . to . equal ( SeverityNumber . ERROR ) ;
169213 expect ( log . body ) . to . equal ( 'Unknown error type: number' ) ;
170214 expect ( log . attributes ) . to . deep . equal ( {
171- 'app.version' : 'unset'
215+ [ LOG_ENTRY_ATTRIBUTE_KEYS . APP_VERSION ] : 'unset' ,
216+ [ LOG_ENTRY_ATTRIBUTE_KEYS . SESSION_ID ] : MOCK_SESSION_ID
172217 } ) ;
173218 } ) ;
174219
@@ -195,9 +240,10 @@ describe('Top level API', () => {
195240 expect ( emittedLogs [ 0 ] . attributes ) . to . deep . equal ( {
196241 'error.type' : 'TestError' ,
197242 'error.stack' : '...stack trace...' ,
198- 'app.version' : 'unset' ,
243+ [ LOG_ENTRY_ATTRIBUTE_KEYS . APP_VERSION ] : 'unset' ,
199244 'logging.googleapis.com/trace' : `projects/${ PROJECT_ID } /traces/my-trace` ,
200- 'logging.googleapis.com/spanId' : `my-span`
245+ 'logging.googleapis.com/spanId' : `my-span` ,
246+ [ LOG_ENTRY_ATTRIBUTE_KEYS . SESSION_ID ] : MOCK_SESSION_ID
201247 } ) ;
202248 } ) ;
203249
@@ -220,13 +266,14 @@ describe('Top level API', () => {
220266 expect ( log . attributes ) . to . deep . equal ( {
221267 'error.type' : 'TestError' ,
222268 'error.stack' : '...stack trace...' ,
223- 'app.version' : 'unset' ,
269+ [ LOG_ENTRY_ATTRIBUTE_KEYS . APP_VERSION ] : 'unset' ,
224270 strAttr : 'string attribute' ,
225271 mapAttr : {
226272 boolAttr : true ,
227273 numAttr : 2
228274 } ,
229- arrAttr : [ 1 , 2 , 3 ]
275+ arrAttr : [ 1 , 2 , 3 ] ,
276+ [ LOG_ENTRY_ATTRIBUTE_KEYS . SESSION_ID ] : MOCK_SESSION_ID
230277 } ) ;
231278 } ) ;
232279
@@ -244,7 +291,75 @@ describe('Top level API', () => {
244291 expect ( emittedLogs . length ) . to . equal ( 1 ) ;
245292 const log = emittedLogs [ 0 ] ;
246293 expect ( log . attributes ) . to . deep . equal ( {
247- 'app.version' : '1.0.0'
294+ [ LOG_ENTRY_ATTRIBUTE_KEYS . APP_VERSION ] : '1.0.0' ,
295+ [ LOG_ENTRY_ATTRIBUTE_KEYS . SESSION_ID ] : MOCK_SESSION_ID
296+ } ) ;
297+ } ) ;
298+
299+ describe ( 'Session Metadata' , ( ) => {
300+ it ( 'should generate and store a new session ID if none exists' , ( ) => {
301+ captureError ( fakeTelemetry , 'error' ) ;
302+
303+ expect ( emittedLogs . length ) . to . equal ( 1 ) ;
304+ const log = emittedLogs [ 0 ] ;
305+ expect ( log . attributes ! [ LOG_ENTRY_ATTRIBUTE_KEYS . SESSION_ID ] ) . to . equal (
306+ MOCK_SESSION_ID
307+ ) ;
308+ expect ( storage [ TELEMETRY_SESSION_ID_KEY ] ) . to . equal ( MOCK_SESSION_ID ) ;
309+ } ) ;
310+
311+ it ( 'should retrieve existing session ID from sessionStorage' , ( ) => {
312+ storage [ TELEMETRY_SESSION_ID_KEY ] = 'existing-session-id' ;
313+
314+ captureError ( fakeTelemetry , 'error' ) ;
315+
316+ expect ( emittedLogs . length ) . to . equal ( 1 ) ;
317+ const log = emittedLogs [ 0 ] ;
318+ expect ( log . attributes ! [ LOG_ENTRY_ATTRIBUTE_KEYS . SESSION_ID ] ) . to . equal (
319+ 'existing-session-id'
320+ ) ;
321+ } ) ;
322+
323+ it ( 'should handle errors when sessionStorage.getItem throws' , ( ) => {
324+ const sessionStorageMock : Partial < Storage > = {
325+ getItem : ( ) => {
326+ throw new Error ( 'SecurityError' ) ;
327+ } ,
328+ setItem : ( ) => { }
329+ } ;
330+
331+ Object . defineProperty ( global , 'sessionStorage' , {
332+ value : sessionStorageMock ,
333+ writable : true
334+ } ) ;
335+
336+ captureError ( fakeTelemetry , 'error' ) ;
337+
338+ expect ( emittedLogs . length ) . to . equal ( 1 ) ;
339+ const log = emittedLogs [ 0 ] ;
340+ expect ( log . attributes ! [ LOG_ENTRY_ATTRIBUTE_KEYS . SESSION_ID ] ) . to . be
341+ . undefined ;
342+ } ) ;
343+
344+ it ( 'should handle errors when sessionStorage.setItem throws' , ( ) => {
345+ const sessionStorageMock : Partial < Storage > = {
346+ getItem : ( ) => null , // Emulate no existing session ID
347+ setItem : ( ) => {
348+ throw new Error ( 'SecurityError' ) ;
349+ }
350+ } ;
351+
352+ Object . defineProperty ( global , 'sessionStorage' , {
353+ value : sessionStorageMock ,
354+ writable : true
355+ } ) ;
356+
357+ captureError ( fakeTelemetry , 'error' ) ;
358+
359+ expect ( emittedLogs . length ) . to . equal ( 1 ) ;
360+ const log = emittedLogs [ 0 ] ;
361+ expect ( log . attributes ! [ LOG_ENTRY_ATTRIBUTE_KEYS . SESSION_ID ] ) . to . be
362+ . undefined ;
248363 } ) ;
249364 } ) ;
250365 } ) ;
0 commit comments