@leanbase.com/js 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -12
- package/dist/index.cjs +2976 -60
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +760 -11
- package/dist/index.mjs +2977 -61
- package/dist/index.mjs.map +1 -1
- package/dist/leanbase.iife.js +3093 -134
- package/dist/leanbase.iife.js.map +1 -1
- package/package.json +45 -45
- package/src/autocapture-utils.ts +550 -0
- package/src/autocapture.ts +415 -0
- package/src/config.ts +8 -0
- package/src/constants.ts +108 -0
- package/src/extensions/rageclick.ts +34 -0
- package/src/iife.ts +31 -27
- package/src/index.ts +1 -1
- package/src/leanbase-logger.ts +7 -4
- package/src/leanbase-persistence.ts +374 -0
- package/src/leanbase.ts +366 -71
- package/src/page-view.ts +124 -0
- package/src/scroll-manager.ts +103 -0
- package/src/session-props.ts +114 -0
- package/src/sessionid.ts +330 -0
- package/src/storage.ts +410 -0
- package/src/types.ts +634 -0
- package/src/utils/blocked-uas.ts +162 -0
- package/src/utils/element-utils.ts +50 -0
- package/src/utils/event-utils.ts +304 -0
- package/src/utils/index.ts +222 -0
- package/src/utils/request-utils.ts +128 -0
- package/src/utils/simple-event-emitter.ts +27 -0
- package/src/utils/user-agent-utils.ts +357 -0
- package/src/uuidv7.ts +268 -0
- package/src/version.ts +1 -1
- package/LICENSE +0 -37
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
export const DEFAULT_BLOCKED_UA_STRS = [
|
|
2
|
+
// Random assortment of bots
|
|
3
|
+
'amazonbot',
|
|
4
|
+
'amazonproductbot',
|
|
5
|
+
'app.hypefactors.com', // Buck, but "buck" is too short to be safe to block (https://app.hypefactors.com/media-monitoring/about.htm)
|
|
6
|
+
'applebot',
|
|
7
|
+
'archive.org_bot',
|
|
8
|
+
'awariobot',
|
|
9
|
+
'backlinksextendedbot',
|
|
10
|
+
'baiduspider',
|
|
11
|
+
'bingbot',
|
|
12
|
+
'bingpreview',
|
|
13
|
+
'chrome-lighthouse',
|
|
14
|
+
'dataforseobot',
|
|
15
|
+
'deepscan',
|
|
16
|
+
'duckduckbot',
|
|
17
|
+
'facebookexternal',
|
|
18
|
+
'facebookcatalog',
|
|
19
|
+
'http://yandex.com/bots',
|
|
20
|
+
'hubspot',
|
|
21
|
+
'ia_archiver',
|
|
22
|
+
'leikibot',
|
|
23
|
+
'linkedinbot',
|
|
24
|
+
'meta-externalagent',
|
|
25
|
+
'mj12bot',
|
|
26
|
+
'msnbot',
|
|
27
|
+
'nessus',
|
|
28
|
+
'petalbot',
|
|
29
|
+
'pinterest',
|
|
30
|
+
'prerender',
|
|
31
|
+
'rogerbot',
|
|
32
|
+
'screaming frog',
|
|
33
|
+
'sebot-wa',
|
|
34
|
+
'sitebulb',
|
|
35
|
+
'slackbot',
|
|
36
|
+
'slurp',
|
|
37
|
+
'trendictionbot',
|
|
38
|
+
'turnitin',
|
|
39
|
+
'twitterbot',
|
|
40
|
+
'vercel-screenshot',
|
|
41
|
+
'vercelbot',
|
|
42
|
+
'yahoo! slurp',
|
|
43
|
+
'yandexbot',
|
|
44
|
+
'zoombot',
|
|
45
|
+
|
|
46
|
+
// Bot-like words, maybe we should block `bot` entirely?
|
|
47
|
+
'bot.htm',
|
|
48
|
+
'bot.php',
|
|
49
|
+
'(bot;',
|
|
50
|
+
'bot/',
|
|
51
|
+
'crawler',
|
|
52
|
+
|
|
53
|
+
// Ahrefs: https://ahrefs.com/seo/glossary/ahrefsbot
|
|
54
|
+
'ahrefsbot',
|
|
55
|
+
'ahrefssiteaudit',
|
|
56
|
+
|
|
57
|
+
// Semrush bots: https://www.semrush.com/bot/
|
|
58
|
+
'semrushbot',
|
|
59
|
+
'siteauditbot',
|
|
60
|
+
'splitsignalbot',
|
|
61
|
+
|
|
62
|
+
// AI Crawlers
|
|
63
|
+
'gptbot',
|
|
64
|
+
'oai-searchbot',
|
|
65
|
+
'chatgpt-user',
|
|
66
|
+
'perplexitybot',
|
|
67
|
+
|
|
68
|
+
// Uptime-like stuff
|
|
69
|
+
'better uptime bot',
|
|
70
|
+
'sentryuptimebot',
|
|
71
|
+
'uptimerobot',
|
|
72
|
+
|
|
73
|
+
// headless browsers
|
|
74
|
+
'headlesschrome',
|
|
75
|
+
'cypress',
|
|
76
|
+
// we don't block electron here, as many customers use posthog-js in electron apps
|
|
77
|
+
|
|
78
|
+
// a whole bunch of goog-specific crawlers
|
|
79
|
+
// https://developers.google.com/search/docs/advanced/crawling/overview-google-crawlers
|
|
80
|
+
'google-hoteladsverifier',
|
|
81
|
+
'adsbot-google',
|
|
82
|
+
'apis-google',
|
|
83
|
+
'duplexweb-google',
|
|
84
|
+
'feedfetcher-google',
|
|
85
|
+
'google favicon',
|
|
86
|
+
'google web preview',
|
|
87
|
+
'google-read-aloud',
|
|
88
|
+
'googlebot',
|
|
89
|
+
'googleother',
|
|
90
|
+
'google-cloudvertexbot',
|
|
91
|
+
'googleweblight',
|
|
92
|
+
'mediapartners-google',
|
|
93
|
+
'storebot-google',
|
|
94
|
+
'google-inspectiontool',
|
|
95
|
+
'bytespider',
|
|
96
|
+
]
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Block various web spiders from executing our JS and sending false capturing data
|
|
100
|
+
*/
|
|
101
|
+
export const isBlockedUA = function (ua: string, customBlockedUserAgents: string[]): boolean {
|
|
102
|
+
if (!ua) {
|
|
103
|
+
return false
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const uaLower = ua.toLowerCase()
|
|
107
|
+
return DEFAULT_BLOCKED_UA_STRS.concat(customBlockedUserAgents || []).some((blockedUA) => {
|
|
108
|
+
const blockedUaLower = blockedUA.toLowerCase()
|
|
109
|
+
|
|
110
|
+
// can't use includes because IE 11 :/
|
|
111
|
+
return uaLower.indexOf(blockedUaLower) !== -1
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// There's more in the type, but this is all we use. It's currently experimental, see
|
|
116
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/Navigator/userAgentData
|
|
117
|
+
// if you're reading this in the future, when it's no longer experimental, please remove this type and use an official one.
|
|
118
|
+
// Be extremely defensive here to ensure backwards and *forwards* compatibility, and remove this defensiveness in the
|
|
119
|
+
// future when it is safe to do so.
|
|
120
|
+
export interface NavigatorUAData {
|
|
121
|
+
brands?: {
|
|
122
|
+
brand: string
|
|
123
|
+
version: string
|
|
124
|
+
}[]
|
|
125
|
+
}
|
|
126
|
+
declare global {
|
|
127
|
+
interface Navigator {
|
|
128
|
+
userAgentData?: NavigatorUAData
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export const isLikelyBot = function (navigator: Navigator | undefined, customBlockedUserAgents: string[]): boolean {
|
|
133
|
+
if (!navigator) {
|
|
134
|
+
return false
|
|
135
|
+
}
|
|
136
|
+
const ua = navigator.userAgent
|
|
137
|
+
if (ua) {
|
|
138
|
+
if (isBlockedUA(ua, customBlockedUserAgents)) {
|
|
139
|
+
return true
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
try {
|
|
143
|
+
// eslint-disable-next-line compat/compat
|
|
144
|
+
const uaData = navigator?.userAgentData as NavigatorUAData
|
|
145
|
+
if (uaData?.brands && uaData.brands.some((brandObj) => isBlockedUA(brandObj?.brand, customBlockedUserAgents))) {
|
|
146
|
+
return true
|
|
147
|
+
}
|
|
148
|
+
} catch {
|
|
149
|
+
// ignore the error, we were using experimental browser features
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return !!navigator.webdriver
|
|
153
|
+
|
|
154
|
+
// There's some more enhancements we could make in this area, e.g. it's possible to check if Chrome dev tools are
|
|
155
|
+
// open, which will detect some bots that are trying to mask themselves and might get past the checks above.
|
|
156
|
+
// However, this would give false positives for actual humans who have dev tools open.
|
|
157
|
+
|
|
158
|
+
// We could also use the data in navigator.userAgentData.getHighEntropyValues() to detect bots, but we should wait
|
|
159
|
+
// until this stops being experimental. The MDN docs imply that this might eventually require user permission.
|
|
160
|
+
// See https://developer.mozilla.org/en-US/docs/Web/API/NavigatorUAData/getHighEntropyValues
|
|
161
|
+
// It would be very bad if posthog-js caused a permission prompt to appear on every page load.
|
|
162
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { TOOLBAR_CONTAINER_CLASS, TOOLBAR_ID } from '../constants'
|
|
2
|
+
|
|
3
|
+
export function isElementInToolbar(el: EventTarget | null): boolean {
|
|
4
|
+
if (el instanceof Element) {
|
|
5
|
+
// closest isn't available in IE11, but we'll polyfill when bundling
|
|
6
|
+
return el.id === TOOLBAR_ID || !!el.closest?.('.' + TOOLBAR_CONTAINER_CLASS)
|
|
7
|
+
}
|
|
8
|
+
return false
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/*
|
|
12
|
+
* Check whether an element has nodeType Node.ELEMENT_NODE
|
|
13
|
+
* @param {Element} el - element to check
|
|
14
|
+
* @returns {boolean} whether el is of the correct nodeType
|
|
15
|
+
*/
|
|
16
|
+
export function isElementNode(el: Node | Element | undefined | null): el is Element {
|
|
17
|
+
return !!el && el.nodeType === 1 // Node.ELEMENT_NODE - use integer constant for browser portability
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/*
|
|
21
|
+
* Check whether an element is of a given tag type.
|
|
22
|
+
* Due to potential reference discrepancies (such as the webcomponents.js polyfill),
|
|
23
|
+
* we want to match tagNames instead of specific references because something like
|
|
24
|
+
* element === document.body won't always work because element might not be a native
|
|
25
|
+
* element.
|
|
26
|
+
* @param {Element} el - element to check
|
|
27
|
+
* @param {string} tag - tag name (e.g., "div")
|
|
28
|
+
* @returns {boolean} whether el is of the given tag type
|
|
29
|
+
*/
|
|
30
|
+
export function isTag(el: Element | undefined | null, tag: string): el is HTMLElement {
|
|
31
|
+
return !!el && !!el.tagName && el.tagName.toLowerCase() === tag.toLowerCase()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/*
|
|
35
|
+
* Check whether an element has nodeType Node.TEXT_NODE
|
|
36
|
+
* @param {Element} el - element to check
|
|
37
|
+
* @returns {boolean} whether el is of the correct nodeType
|
|
38
|
+
*/
|
|
39
|
+
export function isTextNode(el: Element | undefined | null): el is HTMLElement {
|
|
40
|
+
return !!el && el.nodeType === 3 // Node.TEXT_NODE - use integer constant for browser portability
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/*
|
|
44
|
+
* Check whether an element has nodeType Node.DOCUMENT_FRAGMENT_NODE
|
|
45
|
+
* @param {Element} el - element to check
|
|
46
|
+
* @returns {boolean} whether el is of the correct nodeType
|
|
47
|
+
*/
|
|
48
|
+
export function isDocumentFragment(el: Element | ParentNode | undefined | null): el is DocumentFragment {
|
|
49
|
+
return !!el && el.nodeType === 11 // Node.DOCUMENT_FRAGMENT_NODE - use integer constant for browser portability
|
|
50
|
+
}
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import { convertToURL, getQueryParam, maskQueryParams } from './request-utils'
|
|
2
|
+
import { isNull, stripLeadingDollar } from '@posthog/core'
|
|
3
|
+
import { each, extend, extendArray, stripEmptyProperties } from './index'
|
|
4
|
+
import { document, location, userAgent, window } from './'
|
|
5
|
+
import { detectBrowser, detectBrowserVersion, detectDevice, detectDeviceType, detectOS } from './user-agent-utils'
|
|
6
|
+
import { cookieStore } from '../storage'
|
|
7
|
+
import Config from '../config'
|
|
8
|
+
import { Properties } from '../types'
|
|
9
|
+
|
|
10
|
+
const URL_REGEX_PREFIX = 'https?://(.*)'
|
|
11
|
+
|
|
12
|
+
// CAMPAIGN_PARAMS and EVENT_TO_PERSON_PROPERTIES should be kept in sync with
|
|
13
|
+
// https://github.com/PostHog/posthog/blob/master/plugin-server/src/utils/db/utils.ts#L60
|
|
14
|
+
|
|
15
|
+
// The list of campaign parameters that could be considered personal data under e.g. GDPR.
|
|
16
|
+
// These can be masked in URLs and properties before being sent to posthog.
|
|
17
|
+
export const PERSONAL_DATA_CAMPAIGN_PARAMS = [
|
|
18
|
+
'gclid', // google ads
|
|
19
|
+
'gclsrc', // google ads 360
|
|
20
|
+
'dclid', // google display ads
|
|
21
|
+
'gbraid', // google ads, web to app
|
|
22
|
+
'wbraid', // google ads, app to web
|
|
23
|
+
'fbclid', // facebook
|
|
24
|
+
'msclkid', // microsoft
|
|
25
|
+
'twclid', // twitter
|
|
26
|
+
'li_fat_id', // linkedin
|
|
27
|
+
'igshid', // instagram
|
|
28
|
+
'ttclid', // tiktok
|
|
29
|
+
'rdt_cid', // reddit
|
|
30
|
+
'epik', // pinterest
|
|
31
|
+
'qclid', // quora
|
|
32
|
+
'sccid', // snapchat
|
|
33
|
+
'irclid', // impact
|
|
34
|
+
'_kx', // klaviyo
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
export const CAMPAIGN_PARAMS = extendArray(
|
|
38
|
+
[
|
|
39
|
+
'utm_source',
|
|
40
|
+
'utm_medium',
|
|
41
|
+
'utm_campaign',
|
|
42
|
+
'utm_content',
|
|
43
|
+
'utm_term',
|
|
44
|
+
'gad_source', // google ads source
|
|
45
|
+
'mc_cid', // mailchimp campaign id
|
|
46
|
+
],
|
|
47
|
+
PERSONAL_DATA_CAMPAIGN_PARAMS
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
export const EVENT_TO_PERSON_PROPERTIES = [
|
|
51
|
+
// mobile params
|
|
52
|
+
'$app_build',
|
|
53
|
+
'$app_name',
|
|
54
|
+
'$app_namespace',
|
|
55
|
+
'$app_version',
|
|
56
|
+
// web params
|
|
57
|
+
'$browser',
|
|
58
|
+
'$browser_version',
|
|
59
|
+
'$device_type',
|
|
60
|
+
'$current_url',
|
|
61
|
+
'$pathname',
|
|
62
|
+
'$os',
|
|
63
|
+
'$os_name', // $os_name is a special case, it's treated as an alias of $os!
|
|
64
|
+
'$os_version',
|
|
65
|
+
'$referring_domain',
|
|
66
|
+
'$referrer',
|
|
67
|
+
'$screen_height',
|
|
68
|
+
'$screen_width',
|
|
69
|
+
'$viewport_height',
|
|
70
|
+
'$viewport_width',
|
|
71
|
+
'$raw_user_agent',
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
export const MASKED = '<masked>'
|
|
75
|
+
|
|
76
|
+
// Campaign params that can be read from the cookie store
|
|
77
|
+
export const COOKIE_CAMPAIGN_PARAMS = [
|
|
78
|
+
'li_fat_id', // linkedin
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
export function getCampaignParams(
|
|
82
|
+
customTrackedParams?: string[],
|
|
83
|
+
maskPersonalDataProperties?: boolean,
|
|
84
|
+
customPersonalDataProperties?: string[] | undefined
|
|
85
|
+
): Record<string, string> {
|
|
86
|
+
if (!document) {
|
|
87
|
+
return {}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const paramsToMask = maskPersonalDataProperties
|
|
91
|
+
? extendArray([], PERSONAL_DATA_CAMPAIGN_PARAMS, customPersonalDataProperties || [])
|
|
92
|
+
: []
|
|
93
|
+
|
|
94
|
+
// Initially get campaign params from the URL
|
|
95
|
+
const urlCampaignParams = _getCampaignParamsFromUrl(
|
|
96
|
+
maskQueryParams(document.URL, paramsToMask, MASKED),
|
|
97
|
+
customTrackedParams
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
// But we can also get some of them from the cookie store
|
|
101
|
+
// For example: https://learn.microsoft.com/en-us/linkedin/marketing/conversions/enabling-first-party-cookies?view=li-lms-2025-05#reading-li_fat_id-from-cookies
|
|
102
|
+
const cookieCampaignParams = _getCampaignParamsFromCookie()
|
|
103
|
+
|
|
104
|
+
// Prefer the values found in the urlCampaignParams if possible
|
|
105
|
+
// `extend` will override the values if found in the second argument
|
|
106
|
+
return extend(cookieCampaignParams, urlCampaignParams)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function _getCampaignParamsFromUrl(url: string, customParams?: string[]): Record<string, string> {
|
|
110
|
+
const campaign_keywords = CAMPAIGN_PARAMS.concat(customParams || [])
|
|
111
|
+
|
|
112
|
+
const params: Record<string, any> = {}
|
|
113
|
+
each(campaign_keywords, function (kwkey) {
|
|
114
|
+
const kw = getQueryParam(url, kwkey)
|
|
115
|
+
params[kwkey] = kw ? kw : null
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
return params
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function _getCampaignParamsFromCookie(): Record<string, string> {
|
|
122
|
+
const params: Record<string, any> = {}
|
|
123
|
+
each(COOKIE_CAMPAIGN_PARAMS, function (kwkey) {
|
|
124
|
+
const kw = cookieStore._get(kwkey)
|
|
125
|
+
params[kwkey] = kw ? kw : null
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
return params
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function _getSearchEngine(referrer: string): string | null {
|
|
132
|
+
if (!referrer) {
|
|
133
|
+
return null
|
|
134
|
+
} else {
|
|
135
|
+
if (referrer.search(URL_REGEX_PREFIX + 'google.([^/?]*)') === 0) {
|
|
136
|
+
return 'google'
|
|
137
|
+
} else if (referrer.search(URL_REGEX_PREFIX + 'bing.com') === 0) {
|
|
138
|
+
return 'bing'
|
|
139
|
+
} else if (referrer.search(URL_REGEX_PREFIX + 'yahoo.com') === 0) {
|
|
140
|
+
return 'yahoo'
|
|
141
|
+
} else if (referrer.search(URL_REGEX_PREFIX + 'duckduckgo.com') === 0) {
|
|
142
|
+
return 'duckduckgo'
|
|
143
|
+
} else {
|
|
144
|
+
return null
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function _getSearchInfoFromReferrer(referrer: string): Record<string, any> {
|
|
150
|
+
const search = _getSearchEngine(referrer)
|
|
151
|
+
const param = search != 'yahoo' ? 'q' : 'p'
|
|
152
|
+
const ret: Record<string, any> = {}
|
|
153
|
+
|
|
154
|
+
if (!isNull(search)) {
|
|
155
|
+
ret['$search_engine'] = search
|
|
156
|
+
|
|
157
|
+
const keyword = document ? getQueryParam(document.referrer, param) : ''
|
|
158
|
+
if (keyword.length) {
|
|
159
|
+
ret['ph_keyword'] = keyword
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return ret
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function getSearchInfo(): Record<string, any> {
|
|
167
|
+
const referrer = document?.referrer
|
|
168
|
+
if (!referrer) {
|
|
169
|
+
return {}
|
|
170
|
+
}
|
|
171
|
+
return _getSearchInfoFromReferrer(referrer)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function getBrowserLanguage(): string | undefined {
|
|
175
|
+
return (
|
|
176
|
+
navigator.language || // Any modern browser
|
|
177
|
+
(navigator as Record<string, any>).userLanguage // IE11
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function getBrowserLanguagePrefix(): string | undefined {
|
|
182
|
+
const lang = getBrowserLanguage()
|
|
183
|
+
return typeof lang === 'string' ? lang.split('-')[0] : undefined
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function getReferrer(): string {
|
|
187
|
+
return document?.referrer || '$direct'
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function getReferringDomain(): string {
|
|
191
|
+
if (!document?.referrer) {
|
|
192
|
+
return '$direct'
|
|
193
|
+
}
|
|
194
|
+
return convertToURL(document.referrer)?.host || '$direct'
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export function getReferrerInfo(): Record<string, any> {
|
|
198
|
+
return {
|
|
199
|
+
$referrer: getReferrer(),
|
|
200
|
+
$referring_domain: getReferringDomain(),
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function getPersonInfo(maskPersonalDataProperties?: boolean, customPersonalDataProperties?: string[]) {
|
|
205
|
+
const paramsToMask = maskPersonalDataProperties
|
|
206
|
+
? extendArray([], PERSONAL_DATA_CAMPAIGN_PARAMS, customPersonalDataProperties || [])
|
|
207
|
+
: []
|
|
208
|
+
const url = location?.href.substring(0, 1000)
|
|
209
|
+
// we're being a bit more economical with bytes here because this is stored in the cookie
|
|
210
|
+
return {
|
|
211
|
+
r: getReferrer().substring(0, 1000),
|
|
212
|
+
u: url ? maskQueryParams(url, paramsToMask, MASKED) : undefined,
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function getPersonPropsFromInfo(info: Record<string, any>): Record<string, any> {
|
|
217
|
+
const { r: referrer, u: url } = info
|
|
218
|
+
const referring_domain =
|
|
219
|
+
referrer == null ? undefined : referrer == '$direct' ? '$direct' : convertToURL(referrer)?.host
|
|
220
|
+
|
|
221
|
+
const props: Record<string, string | undefined> = {
|
|
222
|
+
$referrer: referrer,
|
|
223
|
+
$referring_domain: referring_domain,
|
|
224
|
+
}
|
|
225
|
+
if (url) {
|
|
226
|
+
props['$current_url'] = url
|
|
227
|
+
const location = convertToURL(url)
|
|
228
|
+
props['$host'] = location?.host
|
|
229
|
+
props['$pathname'] = location?.pathname
|
|
230
|
+
const campaignParams = _getCampaignParamsFromUrl(url)
|
|
231
|
+
extend(props, campaignParams)
|
|
232
|
+
}
|
|
233
|
+
if (referrer) {
|
|
234
|
+
const searchInfo = _getSearchInfoFromReferrer(referrer)
|
|
235
|
+
extend(props, searchInfo)
|
|
236
|
+
}
|
|
237
|
+
return props
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export function getInitialPersonPropsFromInfo(info: Record<string, any>): Record<string, any> {
|
|
241
|
+
const personProps = getPersonPropsFromInfo(info)
|
|
242
|
+
const props: Record<string, any> = {}
|
|
243
|
+
each(personProps, function (val: any, key: string) {
|
|
244
|
+
props[`$initial_${stripLeadingDollar(key)}`] = val
|
|
245
|
+
})
|
|
246
|
+
return props
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export function getTimezone(): string | undefined {
|
|
250
|
+
try {
|
|
251
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
252
|
+
} catch {
|
|
253
|
+
return undefined
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export function getTimezoneOffset(): number | undefined {
|
|
258
|
+
try {
|
|
259
|
+
return new Date().getTimezoneOffset()
|
|
260
|
+
} catch {
|
|
261
|
+
return undefined
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export function getEventProperties(
|
|
266
|
+
maskPersonalDataProperties?: boolean,
|
|
267
|
+
customPersonalDataProperties?: string[]
|
|
268
|
+
): Properties {
|
|
269
|
+
if (!userAgent) {
|
|
270
|
+
return {}
|
|
271
|
+
}
|
|
272
|
+
const paramsToMask = maskPersonalDataProperties
|
|
273
|
+
? extendArray([], PERSONAL_DATA_CAMPAIGN_PARAMS, customPersonalDataProperties || [])
|
|
274
|
+
: []
|
|
275
|
+
const [os_name, os_version] = detectOS(userAgent)
|
|
276
|
+
return extend(
|
|
277
|
+
stripEmptyProperties({
|
|
278
|
+
$os: os_name,
|
|
279
|
+
$os_version: os_version,
|
|
280
|
+
$browser: detectBrowser(userAgent, navigator.vendor),
|
|
281
|
+
$device: detectDevice(userAgent),
|
|
282
|
+
$device_type: detectDeviceType(userAgent),
|
|
283
|
+
$timezone: getTimezone(),
|
|
284
|
+
$timezone_offset: getTimezoneOffset(),
|
|
285
|
+
}),
|
|
286
|
+
{
|
|
287
|
+
$current_url: maskQueryParams(location?.href, paramsToMask, MASKED),
|
|
288
|
+
$host: location?.host,
|
|
289
|
+
$pathname: location?.pathname,
|
|
290
|
+
$raw_user_agent: userAgent.length > 1000 ? userAgent.substring(0, 997) + '...' : userAgent,
|
|
291
|
+
$browser_version: detectBrowserVersion(userAgent, navigator.vendor),
|
|
292
|
+
$browser_language: getBrowserLanguage(),
|
|
293
|
+
$browser_language_prefix: getBrowserLanguagePrefix(),
|
|
294
|
+
$screen_height: window?.screen.height,
|
|
295
|
+
$screen_width: window?.screen.width,
|
|
296
|
+
$viewport_height: window?.innerHeight,
|
|
297
|
+
$viewport_width: window?.innerWidth,
|
|
298
|
+
$lib: 'web',
|
|
299
|
+
$lib_version: Config.LIB_VERSION,
|
|
300
|
+
$insert_id: Math.random().toString(36).substring(2, 10) + Math.random().toString(36).substring(2, 10),
|
|
301
|
+
$time: Date.now() / 1000, // epoch time in seconds
|
|
302
|
+
}
|
|
303
|
+
)
|
|
304
|
+
}
|