1+ import { IInjector } from "@paperbits/common/injection" ;
2+ import { Logger } from "@paperbits/common/logging" ;
3+ import { Utils } from "../utils" ;
4+ import { USER_ACTION , USER_ID , USER_SESSION } from "../constants" ;
5+
6+ const TrackingEventElements = [ "BUTTON" , "A" ] ;
7+
8+ export class TelemetryConfigurator {
9+
10+ constructor ( private injector : IInjector ) {
11+ // required for user session init.
12+ console . log ( "TelemetryConfigurator initialized with userId: " + this . userId + " and sessionId: " + this . sessionId ) ;
13+ }
14+
15+ public get userId ( ) : string {
16+ const sessionCookie = Utils . getCookie ( USER_ID ) ;
17+ if ( sessionCookie ) {
18+ return sessionCookie . value ;
19+ } else {
20+ const newId = Utils . guid ( ) ;
21+ Utils . setCookie ( USER_ID , newId , 400 ) ; // set cookie for 400 days - maximum allowed by the browser
22+ return newId ;
23+ }
24+ }
25+
26+ public get sessionId ( ) : string {
27+ const sessionId = sessionStorage . getItem ( USER_SESSION ) ;
28+ if ( sessionId ) {
29+ return sessionId ;
30+ } else {
31+ const newId = Utils . guid ( ) ;
32+ sessionStorage . setItem ( USER_SESSION , newId ) ;
33+ return newId ;
34+ }
35+ }
36+
37+ public configure ( ) : void {
38+ const logger = this . injector . resolve < Logger > ( "logger" ) ;
39+ // Register service worker for network telemetry.
40+ if ( "serviceWorker" in navigator ) {
41+ navigator . serviceWorker . register ( "/serviceWorker.js" , { scope : "/" } ) . then ( registration => {
42+ console . log ( "Service Worker registered with scope:" , registration . scope ) ;
43+ } ) . catch ( error => {
44+ console . error ( "Service Worker registration failed:" , error ) ;
45+ logger . trackError ( error ) ;
46+ } ) ;
47+
48+ // Listen for messages from the service worker
49+ navigator . serviceWorker . addEventListener ( 'message' , ( event ) => {
50+ console . log ( 'Received message from Service Worker:' , event . data ) ;
51+ if ( event . data ) {
52+ logger . trackEvent ( "NetworkRequest" , event . data ) ;
53+ } else {
54+ console . error ( "No telemetry data received from Service Worker." ) ;
55+ }
56+ } ) ;
57+ }
58+
59+ // Init page load telemetry.
60+ window . onload = ( ) => {
61+ if ( logger ) {
62+ const observer = new PerformanceObserver ( ( list : PerformanceObserverEntryList ) => {
63+ const timing = list . getEntriesByType ( "navigation" ) [ 0 ] as PerformanceNavigationTiming ;
64+ if ( timing ) {
65+ const location = window . location ;
66+ const screenSize = {
67+ width : window . innerWidth . toString ( ) ,
68+ height : window . innerHeight . toString ( )
69+ } ;
70+ const pageLoadTime = timing . loadEventEnd - timing . loadEventStart ;
71+ const domRenderingTime = timing . domComplete - timing . domInteractive ;
72+ const resources = performance . getEntriesByType ( 'resource' ) as PerformanceResourceTiming [ ] ;
73+ const jsCssResources = resources . filter ( resource => {
74+ return resource . initiatorType === 'script' || resource . initiatorType === 'link' ;
75+ } ) ;
76+ const stats = {
77+ pageLoadTime,
78+ domRenderingTime,
79+ jsCssResources : jsCssResources . map ( resource => ( {
80+ name : resource . name ,
81+ duration : resource . duration
82+ } ) )
83+ } ;
84+ logger . trackEvent ( "PageLoad" , { host : location . host , pathName : location . pathname , total : timing . loadEventEnd . toString ( ) , pageLoadStats : JSON . stringify ( stats ) , ...screenSize } ) ;
85+ }
86+ } ) ;
87+ observer . observe ( { type : "navigation" , buffered : true } ) ;
88+ } else {
89+ console . error ( "Logger is not available" ) ;
90+ }
91+ }
92+
93+ document . addEventListener ( "click" , ( event ) => {
94+ this . processUserInteraction ( event ) . then ( ( ) => {
95+ console . log ( "Click processed" ) ;
96+ } ) . catch ( ( error ) => {
97+ console . error ( "Error processing user interaction:" , error ) ;
98+ } ) ;
99+ } ) ;
100+
101+ document . addEventListener ( "keydown" , ( event ) => {
102+ if ( event . key === "Enter" ) {
103+ this . processUserInteraction ( event ) . then ( ( ) => {
104+ console . log ( "Enter key processed" ) ;
105+ } ) . catch ( ( error ) => {
106+ console . error ( "Error processing user interaction:" , error ) ;
107+ } ) ;
108+ }
109+ } ) ;
110+ }
111+
112+ private async processUserInteraction ( event : Event ) {
113+ const element = event . target as HTMLElement ;
114+ const elementTag = element ?. tagName ;
115+ const parent = element ?. parentElement ;
116+ const parentTag = parent ?. tagName ;
117+ if ( ! ( elementTag && TrackingEventElements . includes ( elementTag ) ) && ! ( parentTag && TrackingEventElements . includes ( parentTag ) ) ) {
118+ return ;
119+ }
120+
121+ const eventAction = element . attributes . getNamedItem ( USER_ACTION ) ?. value ;
122+ const eventMessage = {
123+ elementId : element . id
124+ } ;
125+
126+ let navigation = ( ( elementTag === "A" && element ) || ( parentTag === "A" && parent ) ) as HTMLAnchorElement ;
127+
128+ if ( navigation && navigation . href ) {
129+ eventMessage [ "navigationTo" ] = navigation . href ;
130+ eventMessage [ "navigationText" ] = navigation . innerText ;
131+ }
132+
133+ if ( ! eventAction && ! navigation ) {
134+ return ;
135+ }
136+
137+ if ( eventAction ) {
138+ eventMessage [ "eventAction" ] = eventAction ;
139+ }
140+
141+ const logger = this . injector . resolve < Logger > ( "logger" ) ;
142+ await logger . trackEvent ( "UserEvent" , eventMessage ) ;
143+ }
144+ }
0 commit comments