@@ -17,10 +17,9 @@ import { UAParser } from 'ua-parser-js'
17
17
import { getMetadata } from '@hcengineering/platform'
18
18
import presentation from '@hcengineering/presentation'
19
19
import { desktopPlatform , getCurrentLocation } from '@hcengineering/ui'
20
+ import { generateUuid } from '@hcengineering/core'
20
21
import { Analytics } from '@hcengineering/analytics'
21
22
22
- const parser = UAParser ( )
23
-
24
23
let _isSignUp : boolean = false
25
24
export const signupStore = {
26
25
setSignUpFlow : ( isSignUp : boolean ) => {
@@ -31,6 +30,115 @@ export const signupStore = {
31
30
}
32
31
}
33
32
33
+ // Session and activity tracking
34
+ class SessionManager {
35
+ private static instance : SessionManager
36
+ private readonly sessionId : string
37
+ private readonly windowId : string
38
+ private readonly sessionStartTime : number
39
+ private pageviewId : string
40
+ private pageStartTime : number
41
+ private eventCount : number = 0
42
+ private pageViewCount : number = 0
43
+ private currentUrl : string = ''
44
+
45
+ private constructor ( ) {
46
+ this . sessionId = generateUuid ( )
47
+ this . windowId = generateUuid ( )
48
+ this . sessionStartTime = Date . now ( )
49
+ this . pageviewId = generateUuid ( )
50
+ this . pageStartTime = Date . now ( )
51
+ this . currentUrl = window . location . href
52
+ this . pageViewCount = 1 // First page view
53
+
54
+ this . setupNavigationTracking ( )
55
+ }
56
+
57
+ static getInstance ( ) : SessionManager {
58
+ if ( SessionManager . instance === undefined ) {
59
+ SessionManager . instance = new SessionManager ( )
60
+ }
61
+ return SessionManager . instance
62
+ }
63
+
64
+ private setupNavigationTracking ( ) : void {
65
+ window . addEventListener ( 'popstate' , ( ) => {
66
+ setTimeout ( ( ) => {
67
+ this . onUrlChange ( )
68
+ } , 0 )
69
+ } )
70
+ setInterval ( ( ) => {
71
+ if ( window . location . href !== this . currentUrl ) {
72
+ this . onUrlChange ( )
73
+ }
74
+ } , 5000 )
75
+ }
76
+
77
+ onUrlChange ( ) : void {
78
+ if ( window . location . href !== this . currentUrl ) {
79
+ this . currentUrl = window . location . href
80
+ this . pageviewId = generateUuid ( )
81
+ this . pageStartTime = Date . now ( )
82
+ this . pageViewCount ++
83
+ }
84
+ }
85
+
86
+ getSessionData ( ) : Record < string , any > {
87
+ const now = Date . now ( )
88
+ const timeOnPage = Math . floor ( ( now - this . pageStartTime ) / 1000 )
89
+ const sessionDuration = Math . floor ( ( now - this . sessionStartTime ) / 1000 )
90
+
91
+ return {
92
+ $session_id : this . sessionId ,
93
+ $window_id : this . windowId ,
94
+ $pageview_id : this . pageviewId ,
95
+ $session_duration : sessionDuration ,
96
+ $time_on_page : timeOnPage ,
97
+ $page_view_count : this . pageViewCount ,
98
+ $event_count : this . eventCount ,
99
+ $is_first_session : this . isFirstSession ( ) ,
100
+ $is_returning_user : this . isReturningUser ( ) ,
101
+ $insert_id : this . generateInsertId ( )
102
+ }
103
+ }
104
+
105
+ incrementEventCount ( ) : void {
106
+ this . eventCount ++
107
+ }
108
+
109
+ private generateInsertId ( ) : string {
110
+ const timestamp = Date . now ( )
111
+ const random = Math . random ( ) . toString ( 36 ) . substr ( 2 , 9 )
112
+ return `${ timestamp } -${ random } `
113
+ }
114
+
115
+ private isFirstSession ( ) : boolean {
116
+ const hasVisited = localStorage . getItem ( 'analytics_has_visited' )
117
+ if ( hasVisited === null || hasVisited === '' ) {
118
+ localStorage . setItem ( 'analytics_has_visited' , 'true' )
119
+ return true
120
+ }
121
+ return false
122
+ }
123
+
124
+ private isReturningUser ( ) : boolean {
125
+ const firstVisit = localStorage . getItem ( 'analytics_first_visit' )
126
+ const now = Date . now ( )
127
+
128
+ if ( firstVisit === null || firstVisit === '' ) {
129
+ localStorage . setItem ( 'analytics_first_visit' , now . toString ( ) )
130
+ return false
131
+ }
132
+
133
+ const firstVisitTime = parseInt ( firstVisit , 10 )
134
+ return now - firstVisitTime > 24 * 60 * 60 * 1000
135
+ }
136
+ }
137
+
138
+ export function triggerUrlChange ( ) : void {
139
+ SessionManager . getInstance ( ) . onUrlChange ( )
140
+ }
141
+
34
142
function getUrlTrackingParams ( ) : Record < string , string | null > {
35
143
const params = new URLSearchParams ( window . location . search )
36
144
return {
@@ -78,7 +186,16 @@ function getDeviceType (type: string | undefined | null): string {
78
186
}
79
187
80
188
export function collectEventMetadata ( properties : Record < string , any > = { } ) : Record < string , any > {
81
- const trackingParams = getUrlTrackingParams ( )
189
+ const parser = new UAParser ( )
190
+ const browser = parser . getBrowser ( )
191
+ const os = parser . getOS ( )
192
+ const device = parser . getDevice ( )
193
+
194
+ const sessionManager = SessionManager . getInstance ( )
195
+ sessionManager . incrementEventCount ( )
196
+ const sessionData = sessionManager . getSessionData ( )
197
+ const urlTracking = getUrlTrackingParams ( )
198
+ const searchEngine = getSearchEngine ( document . referrer )
82
199
83
200
const referrer = ( ( ) => {
84
201
if ( document . referrer === '' ) return '$direct'
@@ -104,32 +221,42 @@ export function collectEventMetadata (properties: Record<string, any> = {}): Rec
104
221
const browserLanguagePrefix = browserLanguage . split ( '-' ) [ 0 ] !== '' ? browserLanguage . split ( '-' ) [ 0 ] : 'en'
105
222
106
223
return {
107
- ...properties ,
108
- $timestamp : new Date ( ) . toISOString ( ) ,
109
- $os : parser . os . name ?? 'Unknown OS' ,
110
- $os_version : parser . os . version ?? '' ,
111
- $browser : parser . browser . name ?? 'Unknown Browser' ,
112
- $browser_version : parseInt ( parser . browser . major ?? '0' , 10 ) ,
113
- $device_type : getDeviceType ( parser . device . type ) ,
114
- $current_url : window . location . href ,
115
- $host : window . location . hostname ,
116
- $pathname : window . location . pathname ,
224
+ title : document . title ,
225
+ // User agent and platform info
226
+ $lib : desktopPlatform ? 'app' : 'web' ,
227
+ $lib_version : getMetadata ( presentation . metadata . FrontVersion ) ?? '0.0.0' ,
228
+ $browser : browser . name ?? 'Unknown Browser' ,
229
+ $browser_version : parseInt ( browser . major ?? '0' , 10 ) ,
230
+ $browser_language : browserLanguage ,
231
+ $browser_language_prefix : browserLanguagePrefix ,
232
+ $os : os . name ?? 'Unknown OS' ,
233
+ $os_version : os . version ,
234
+ $device_type : getDeviceType ( device . type ) ,
235
+ $device_model : device . model ,
236
+ $device_vendor : device . vendor ,
117
237
$screen_height : window . screen . height ,
118
238
$screen_width : window . screen . width ,
119
239
$viewport_height : window . innerHeight ,
120
240
$viewport_width : window . innerWidth ,
121
- $lib : desktopPlatform ? 'app' : 'web' ,
122
- $lib_version : getMetadata ( presentation . metadata . FrontVersion ) ?? '0.0.0' ,
123
- $search_engine : getSearchEngine ( referrer ) ,
124
- $referrer : referrer ,
125
- $referring_domain : referringDomain ,
241
+ $language : navigator . language ,
126
242
$raw_user_agent : navigator . userAgent ,
127
- title : document . title ,
128
243
$timezone : timezone ,
129
244
$timezone_offset : timezoneOffset ,
130
- $browser_language : browserLanguage ,
131
- $browser_language_prefix : browserLanguagePrefix ,
132
- ...trackingParams
245
+ $timestamp : new Date ( ) . toISOString ( ) ,
246
+ // URL and referrer info
247
+ $current_url : window . location . href ,
248
+ $host : window . location . host ,
249
+ $pathname : window . location . pathname ,
250
+ $search : window . location . search ,
251
+ referrer,
252
+ $referring_domain : referringDomain ,
253
+ $search_engine : searchEngine ,
254
+ // Session and activity fields (PostHog-compatible)
255
+ ...sessionData ,
256
+ // URL tracking parameters (UTM, etc.)
257
+ ...urlTracking ,
258
+ // Custom event parameters
259
+ ...properties
133
260
}
134
261
}
135
262
0 commit comments