1+ import { LDClientLogging } from '../src/api' ;
12import { LDClientTracking } from '../src/api/client/LDClientTracking' ;
23import BrowserTelemetryImpl from '../src/BrowserTelemetryImpl' ;
34import { ParsedOptions } from '../src/options' ;
@@ -22,6 +23,7 @@ const defaultOptions: ParsedOptions = {
2223 } ,
2324 evaluations : true ,
2425 flagChange : true ,
26+ filters : [ ] ,
2527 } ,
2628 stack : {
2729 source : {
@@ -208,3 +210,315 @@ it('unregisters collectors on close', () => {
208210
209211 expect ( mockCollector . unregister ) . toHaveBeenCalled ( ) ;
210212} ) ;
213+
214+ it ( 'logs event dropped message when maxPendingEvents is reached' , ( ) => {
215+ const mockLogger = {
216+ warn : jest . fn ( ) ,
217+ } ;
218+ const telemetry = new BrowserTelemetryImpl ( {
219+ ...defaultOptions ,
220+ maxPendingEvents : 2 ,
221+ logger : mockLogger ,
222+ } ) ;
223+ telemetry . captureError ( new Error ( 'Test error' ) ) ;
224+ expect ( mockLogger . warn ) . not . toHaveBeenCalled ( ) ;
225+ telemetry . captureError ( new Error ( 'Test error 2' ) ) ;
226+ expect ( mockLogger . warn ) . not . toHaveBeenCalled ( ) ;
227+
228+ telemetry . captureError ( new Error ( 'Test error 3' ) ) ;
229+ expect ( mockLogger . warn ) . toHaveBeenCalledWith (
230+ 'LaunchDarkly - Browser Telemetry: Maximum pending events reached. Old events will be dropped until the SDK' +
231+ ' client is registered.' ,
232+ ) ;
233+
234+ telemetry . captureError ( new Error ( 'Test error 4' ) ) ;
235+ expect ( mockLogger . warn ) . toHaveBeenCalledTimes ( 1 ) ;
236+ } ) ;
237+
238+ it ( 'filters breadcrumbs using provided filters' , ( ) => {
239+ const options : ParsedOptions = {
240+ ...defaultOptions ,
241+ breadcrumbs : {
242+ ...defaultOptions . breadcrumbs ,
243+ click : false ,
244+ evaluations : false ,
245+ flagChange : false ,
246+ http : { instrumentFetch : false , instrumentXhr : false } ,
247+ keyboardInput : false ,
248+ filters : [
249+ // Filter to remove breadcrumbs with id:2
250+ ( breadcrumb ) => {
251+ if ( breadcrumb . type === 'custom' && breadcrumb . data ?. id === 2 ) {
252+ return undefined ;
253+ }
254+ return breadcrumb ;
255+ } ,
256+ // Filter to transform breadcrumbs with id:3
257+ ( breadcrumb ) => {
258+ if ( breadcrumb . type === 'custom' && breadcrumb . data ?. id === 3 ) {
259+ return {
260+ ...breadcrumb ,
261+ data : { id : 'filtered-3' } ,
262+ } ;
263+ }
264+ return breadcrumb ;
265+ } ,
266+ ] ,
267+ } ,
268+ } ;
269+ const telemetry = new BrowserTelemetryImpl ( options ) ;
270+
271+ telemetry . addBreadcrumb ( {
272+ type : 'custom' ,
273+ data : { id : 1 } ,
274+ timestamp : Date . now ( ) ,
275+ class : 'custom' ,
276+ level : 'info' ,
277+ } ) ;
278+
279+ telemetry . addBreadcrumb ( {
280+ type : 'custom' ,
281+ data : { id : 2 } ,
282+ timestamp : Date . now ( ) ,
283+ class : 'custom' ,
284+ level : 'info' ,
285+ } ) ;
286+
287+ telemetry . addBreadcrumb ( {
288+ type : 'custom' ,
289+ data : { id : 3 } ,
290+ timestamp : Date . now ( ) ,
291+ class : 'custom' ,
292+ level : 'info' ,
293+ } ) ;
294+
295+ const error = new Error ( 'Test error' ) ;
296+ telemetry . captureError ( error ) ;
297+ telemetry . register ( mockClient ) ;
298+
299+ expect ( mockClient . track ) . toHaveBeenCalledWith (
300+ '$ld:telemetry:error' ,
301+ expect . objectContaining ( {
302+ breadcrumbs : expect . arrayContaining ( [
303+ expect . objectContaining ( { data : { id : 1 } } ) ,
304+ expect . objectContaining ( { data : { id : 'filtered-3' } } ) ,
305+ ] ) ,
306+ } ) ,
307+ ) ;
308+
309+ // Verify breadcrumb with id:2 was filtered out
310+ expect ( mockClient . track ) . toHaveBeenCalledWith (
311+ '$ld:telemetry:error' ,
312+ expect . objectContaining ( {
313+ breadcrumbs : expect . not . arrayContaining ( [ expect . objectContaining ( { data : { id : 2 } } ) ] ) ,
314+ } ) ,
315+ ) ;
316+ } ) ;
317+
318+ it ( 'omits breadcrumb when a filter throws an exception' , ( ) => {
319+ const breadSpy = jest . fn ( ( breadcrumb ) => breadcrumb ) ;
320+ const options : ParsedOptions = {
321+ ...defaultOptions ,
322+ breadcrumbs : {
323+ ...defaultOptions . breadcrumbs ,
324+ filters : [
325+ ( ) => {
326+ throw new Error ( 'Filter error' ) ;
327+ } ,
328+ // This filter should never run
329+ breadSpy ,
330+ ] ,
331+ } ,
332+ } ;
333+ const telemetry = new BrowserTelemetryImpl ( options ) ;
334+
335+ telemetry . addBreadcrumb ( {
336+ type : 'custom' ,
337+ data : { id : 1 } ,
338+ timestamp : Date . now ( ) ,
339+ class : 'custom' ,
340+ level : 'info' ,
341+ } ) ;
342+
343+ const error = new Error ( 'Test error' ) ;
344+ telemetry . captureError ( error ) ;
345+ telemetry . register ( mockClient ) ;
346+
347+ expect ( mockClient . track ) . toHaveBeenCalledWith (
348+ '$ld:telemetry:error' ,
349+ expect . objectContaining ( {
350+ breadcrumbs : [ ] ,
351+ } ) ,
352+ ) ;
353+
354+ expect ( breadSpy ) . not . toHaveBeenCalled ( ) ;
355+ } ) ;
356+
357+ it ( 'omits breadcrumbs when a filter is not a function' , ( ) => {
358+ const options : ParsedOptions = {
359+ ...defaultOptions ,
360+ breadcrumbs : {
361+ ...defaultOptions . breadcrumbs ,
362+ // @ts -ignore
363+ filters : [ 'potato' ] ,
364+ } ,
365+ } ;
366+ const telemetry = new BrowserTelemetryImpl ( options ) ;
367+
368+ telemetry . addBreadcrumb ( {
369+ type : 'custom' ,
370+ data : { id : 1 } ,
371+ timestamp : Date . now ( ) ,
372+ class : 'custom' ,
373+ level : 'info' ,
374+ } ) ;
375+
376+ const error = new Error ( 'Test error' ) ;
377+ telemetry . captureError ( error ) ;
378+ telemetry . register ( mockClient ) ;
379+
380+ expect ( mockClient . track ) . toHaveBeenCalledWith (
381+ '$ld:telemetry:error' ,
382+ expect . objectContaining ( {
383+ breadcrumbs : [ ] ,
384+ } ) ,
385+ ) ;
386+ } ) ;
387+
388+ it ( 'warns when a breadcrumb filter is not a function' , ( ) => {
389+ const mockLogger = {
390+ warn : jest . fn ( ) ,
391+ } ;
392+ const options : ParsedOptions = {
393+ ...defaultOptions ,
394+ // @ts -ignore
395+ breadcrumbs : { ...defaultOptions . breadcrumbs , filters : [ 'potato' ] } ,
396+ logger : mockLogger ,
397+ } ;
398+
399+ const telemetry = new BrowserTelemetryImpl ( options ) ;
400+ telemetry . addBreadcrumb ( {
401+ type : 'custom' ,
402+ data : { id : 1 } ,
403+ timestamp : Date . now ( ) ,
404+ class : 'custom' ,
405+ level : 'info' ,
406+ } ) ;
407+
408+ expect ( mockLogger . warn ) . toHaveBeenCalledWith (
409+ 'LaunchDarkly - Browser Telemetry: Error applying breadcrumb filters: TypeError: filter is not a function' ,
410+ ) ;
411+ } ) ;
412+
413+ it ( 'warns when a breadcrumb filter throws an exception' , ( ) => {
414+ const mockLogger = {
415+ warn : jest . fn ( ) ,
416+ } ;
417+ const options : ParsedOptions = {
418+ ...defaultOptions ,
419+ breadcrumbs : {
420+ ...defaultOptions . breadcrumbs ,
421+ filters : [
422+ ( ) => {
423+ throw new Error ( 'Filter error' ) ;
424+ } ,
425+ ] ,
426+ } ,
427+ logger : mockLogger ,
428+ } ;
429+
430+ const telemetry = new BrowserTelemetryImpl ( options ) ;
431+ telemetry . addBreadcrumb ( {
432+ type : 'custom' ,
433+ data : { id : 1 } ,
434+ timestamp : Date . now ( ) ,
435+ class : 'custom' ,
436+ level : 'info' ,
437+ } ) ;
438+
439+ expect ( mockLogger . warn ) . toHaveBeenCalledWith (
440+ 'LaunchDarkly - Browser Telemetry: Error applying breadcrumb filters: Error: Filter error' ,
441+ ) ;
442+ } ) ;
443+
444+ it ( 'only logs breadcrumb filter error once' , ( ) => {
445+ const mockLogger = {
446+ warn : jest . fn ( ) ,
447+ } ;
448+ const options : ParsedOptions = {
449+ ...defaultOptions ,
450+ breadcrumbs : {
451+ ...defaultOptions . breadcrumbs ,
452+ filters : [
453+ ( ) => {
454+ throw new Error ( 'Filter error' ) ;
455+ } ,
456+ ] ,
457+ } ,
458+ logger : mockLogger ,
459+ } ;
460+
461+ const telemetry = new BrowserTelemetryImpl ( options ) ;
462+
463+ // Add multiple breadcrumbs that will trigger the filter error
464+ telemetry . addBreadcrumb ( {
465+ type : 'custom' ,
466+ data : { id : 1 } ,
467+ timestamp : Date . now ( ) ,
468+ class : 'custom' ,
469+ level : 'info' ,
470+ } ) ;
471+
472+ telemetry . addBreadcrumb ( {
473+ type : 'custom' ,
474+ data : { id : 2 } ,
475+ timestamp : Date . now ( ) ,
476+ class : 'custom' ,
477+ level : 'info' ,
478+ } ) ;
479+
480+ // Verify warning was only logged once
481+ expect ( mockLogger . warn ) . toHaveBeenCalledTimes ( 1 ) ;
482+ expect ( mockLogger . warn ) . toHaveBeenCalledWith (
483+ 'LaunchDarkly - Browser Telemetry: Error applying breadcrumb filters: Error: Filter error' ,
484+ ) ;
485+ } ) ;
486+
487+ it ( 'uses the client logger when no logger is provided' , ( ) => {
488+ const options : ParsedOptions = {
489+ ...defaultOptions ,
490+ breadcrumbs : {
491+ ...defaultOptions . breadcrumbs ,
492+ filters : [
493+ ( ) => {
494+ throw new Error ( 'Filter error' ) ;
495+ } ,
496+ ] ,
497+ } ,
498+ } ;
499+
500+ const telemetry = new BrowserTelemetryImpl ( options ) ;
501+
502+ const mockClientWithLogging : jest . Mocked < LDClientLogging & LDClientTracking > = {
503+ logger : {
504+ warn : jest . fn ( ) ,
505+ } ,
506+ track : jest . fn ( ) ,
507+ } ;
508+
509+ telemetry . register ( mockClientWithLogging ) ;
510+
511+ // Add multiple breadcrumbs that will trigger the filter error
512+ telemetry . addBreadcrumb ( {
513+ type : 'custom' ,
514+ data : { id : 1 } ,
515+ timestamp : Date . now ( ) ,
516+ class : 'custom' ,
517+ level : 'info' ,
518+ } ) ;
519+
520+ expect ( mockClientWithLogging . logger . warn ) . toHaveBeenCalledTimes ( 1 ) ;
521+ expect ( mockClientWithLogging . logger . warn ) . toHaveBeenCalledWith (
522+ 'LaunchDarkly - Browser Telemetry: Error applying breadcrumb filters: Error: Filter error' ,
523+ ) ;
524+ } ) ;
0 commit comments