@newrelic/browser-agent 1.267.0 → 1.268.0
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/CHANGELOG.md +14 -0
- package/dist/cjs/common/config/init.js +3 -0
- package/dist/cjs/common/constants/env.cdn.js +1 -1
- package/dist/cjs/common/constants/env.npm.js +1 -1
- package/dist/cjs/common/dom/iframe.js +10 -0
- package/dist/cjs/common/dom/selector-path.js +48 -0
- package/dist/cjs/common/event-listener/event-listener-opts.js +4 -26
- package/dist/cjs/common/timing/time-keeper.js +9 -0
- package/dist/cjs/common/util/stringify.js +1 -1
- package/dist/cjs/features/generic_events/aggregate/index.js +80 -9
- package/dist/cjs/features/generic_events/aggregate/user-actions/aggregated-user-action.js +39 -0
- package/dist/cjs/features/generic_events/aggregate/user-actions/user-actions-aggregator.js +77 -0
- package/dist/cjs/features/generic_events/constants.js +6 -2
- package/dist/cjs/features/generic_events/instrument/index.js +12 -1
- package/dist/cjs/features/jserrors/aggregate/index.js +2 -2
- package/dist/cjs/features/logging/aggregate/index.js +1 -1
- package/dist/cjs/features/metrics/aggregate/index.js +2 -1
- package/dist/cjs/features/page_view_event/aggregate/index.js +1 -1
- package/dist/cjs/features/session_replay/aggregate/index.js +5 -3
- package/dist/cjs/features/session_trace/aggregate/index.js +6 -4
- package/dist/esm/common/config/init.js +3 -0
- package/dist/esm/common/constants/env.cdn.js +1 -1
- package/dist/esm/common/constants/env.npm.js +1 -1
- package/dist/esm/common/dom/iframe.js +4 -0
- package/dist/esm/common/dom/selector-path.js +41 -0
- package/dist/esm/common/event-listener/event-listener-opts.js +4 -27
- package/dist/esm/common/timing/time-keeper.js +9 -0
- package/dist/esm/common/util/stringify.js +1 -1
- package/dist/esm/features/generic_events/aggregate/index.js +82 -11
- package/dist/esm/features/generic_events/aggregate/user-actions/aggregated-user-action.js +32 -0
- package/dist/esm/features/generic_events/aggregate/user-actions/user-actions-aggregator.js +70 -0
- package/dist/esm/features/generic_events/constants.js +5 -1
- package/dist/esm/features/generic_events/instrument/index.js +14 -3
- package/dist/esm/features/jserrors/aggregate/index.js +2 -2
- package/dist/esm/features/logging/aggregate/index.js +1 -1
- package/dist/esm/features/metrics/aggregate/index.js +2 -1
- package/dist/esm/features/page_view_event/aggregate/index.js +1 -1
- package/dist/esm/features/session_replay/aggregate/index.js +5 -3
- package/dist/esm/features/session_trace/aggregate/index.js +6 -4
- package/dist/types/common/config/init.d.ts.map +1 -1
- package/dist/types/common/dom/iframe.d.ts +2 -0
- package/dist/types/common/dom/iframe.d.ts.map +1 -0
- package/dist/types/common/dom/selector-path.d.ts +2 -0
- package/dist/types/common/dom/selector-path.d.ts.map +1 -0
- package/dist/types/common/event-listener/event-listener-opts.d.ts +2 -2
- package/dist/types/common/event-listener/event-listener-opts.d.ts.map +1 -1
- package/dist/types/common/timing/time-keeper.d.ts +6 -0
- package/dist/types/common/timing/time-keeper.d.ts.map +1 -1
- package/dist/types/features/generic_events/aggregate/index.d.ts +16 -1
- package/dist/types/features/generic_events/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/generic_events/aggregate/user-actions/aggregated-user-action.d.ts +22 -0
- package/dist/types/features/generic_events/aggregate/user-actions/aggregated-user-action.d.ts.map +1 -0
- package/dist/types/features/generic_events/aggregate/user-actions/user-actions-aggregator.d.ts +12 -0
- package/dist/types/features/generic_events/aggregate/user-actions/user-actions-aggregator.d.ts.map +1 -0
- package/dist/types/features/generic_events/constants.d.ts +4 -0
- package/dist/types/features/generic_events/constants.d.ts.map +1 -1
- package/dist/types/features/generic_events/instrument/index.d.ts.map +1 -1
- package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/logging/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/metrics/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/page_view_event/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/common/config/init.js +1 -0
- package/src/common/dom/iframe.js +4 -0
- package/src/common/dom/selector-path.js +45 -0
- package/src/common/event-listener/event-listener-opts.js +5 -30
- package/src/common/timing/__mocks__/time-keeper.js +1 -0
- package/src/common/timing/time-keeper.js +9 -0
- package/src/common/util/stringify.js +1 -1
- package/src/features/generic_events/aggregate/index.js +74 -14
- package/src/features/generic_events/aggregate/user-actions/aggregated-user-action.js +33 -0
- package/src/features/generic_events/aggregate/user-actions/user-actions-aggregator.js +73 -0
- package/src/features/generic_events/constants.js +6 -0
- package/src/features/generic_events/instrument/index.js +19 -3
- package/src/features/jserrors/aggregate/index.js +2 -6
- package/src/features/logging/aggregate/index.js +1 -3
- package/src/features/metrics/aggregate/index.js +2 -1
- package/src/features/page_view_event/aggregate/index.js +1 -3
- package/src/features/session_replay/aggregate/index.js +5 -7
- package/src/features/session_trace/aggregate/index.js +6 -10
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export class AggregatedUserAction {
|
|
2
|
+
constructor(evt: any, selectorPath: any);
|
|
3
|
+
event: any;
|
|
4
|
+
count: number;
|
|
5
|
+
originMs: number;
|
|
6
|
+
relativeMs: number[];
|
|
7
|
+
selectorPath: any;
|
|
8
|
+
rageClick: boolean | undefined;
|
|
9
|
+
/**
|
|
10
|
+
* Aggregates the count and maintains the relative MS array for matching events
|
|
11
|
+
* Will determine if a rage click was observed as part of the aggregation
|
|
12
|
+
* @param {Event} evt
|
|
13
|
+
* @returns {void}
|
|
14
|
+
*/
|
|
15
|
+
aggregate(evt: Event): void;
|
|
16
|
+
/**
|
|
17
|
+
* Determines if the current set of relative ms values constitutes a rage click
|
|
18
|
+
* @returns {boolean}
|
|
19
|
+
*/
|
|
20
|
+
isRageClick(): boolean;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=aggregated-user-action.d.ts.map
|
package/dist/types/features/generic_events/aggregate/user-actions/aggregated-user-action.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aggregated-user-action.d.ts","sourceRoot":"","sources":["../../../../../../src/features/generic_events/aggregate/user-actions/aggregated-user-action.js"],"names":[],"mappings":"AAEA;IACE,yCAOC;IANC,WAAgB;IAChB,cAAc;IACd,iBAAyC;IACzC,qBAAqB;IACrB,kBAAgC;IAChC,+BAA0B;IAG5B;;;;;OAKG;IACH,eAHW,KAAK,GACH,IAAI,CAMhB;IAED;;;OAGG;IACH,eAFa,OAAO,CAKnB;CACF"}
|
package/dist/types/features/generic_events/aggregate/user-actions/user-actions-aggregator.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export class UserActionsAggregator {
|
|
2
|
+
get aggregationEvent(): AggregatedUserAction | undefined;
|
|
3
|
+
/**
|
|
4
|
+
* Process the event and determine if a new aggregation set should be made or if it should increment the current aggregation
|
|
5
|
+
* @param {Event} evt The event supplied by the addEventListener callback
|
|
6
|
+
* @returns {AggregatedUserAction|undefined} The previous aggregation set if it has been completed by processing the current event
|
|
7
|
+
*/
|
|
8
|
+
process(evt: Event): AggregatedUserAction | undefined;
|
|
9
|
+
#private;
|
|
10
|
+
}
|
|
11
|
+
import { AggregatedUserAction } from './aggregated-user-action';
|
|
12
|
+
//# sourceMappingURL=user-actions-aggregator.d.ts.map
|
package/dist/types/features/generic_events/aggregate/user-actions/user-actions-aggregator.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user-actions-aggregator.d.ts","sourceRoot":"","sources":["../../../../../../src/features/generic_events/aggregate/user-actions/user-actions-aggregator.js"],"names":[],"mappings":"AAIA;IAKE,yDAQC;IAED;;;;OAIG;IACH,aAHW,KAAK,GACH,oBAAoB,GAAC,SAAS,CAiB1C;;CACF;qCAtCoC,0BAA0B"}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
export const FEATURE_NAME: string;
|
|
2
2
|
export const IDEAL_PAYLOAD_SIZE: 64000;
|
|
3
3
|
export const MAX_PAYLOAD_SIZE: 1000000;
|
|
4
|
+
export const OBSERVED_EVENTS: string[];
|
|
5
|
+
export const OBSERVED_WINDOW_EVENTS: string[];
|
|
6
|
+
export const RAGE_CLICK_THRESHOLD_EVENTS: 4;
|
|
7
|
+
export const RAGE_CLICK_THRESHOLD_MS: 1000;
|
|
4
8
|
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../../src/features/generic_events/constants.js"],"names":[],"mappings":"AAEA,kCAAuD;AACvD,uCAAuC;AACvC,uCAAuC"}
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../../src/features/generic_events/constants.js"],"names":[],"mappings":"AAEA,kCAAuD;AACvD,uCAAuC;AACvC,uCAAuC;AAEvC,uCAA6F;AAC7F,8CAAuD;AAEvD,4CAA4C;AAC5C,2CAA2C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/generic_events/instrument/index.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/generic_events/instrument/index.js"],"names":[],"mappings":"AAYA;IACE,2BAAiC;IACjC,mEAsBC;CACF;AAED,8CAAuC;+BA9BR,6BAA6B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/jserrors/aggregate/index.js"],"names":[],"mappings":"AA2BA;;GAEG;AAEH;IACE,2BAAiC;IACjC,mDAgCC;IA7BC,kBAAuB;IACvB,eAAoB;IACpB,qBAA0B;IAC1B,2BAAgC;IAChC,uCAA4B;IAC5B,qBAAwB;IA0B1B;;;MA2BC;IAED,qCAWC;IAED,8BAEC;IAED,oEAMC;IAED;;;;;;OAMG;IACH,qCAHW,SAAS,GACP,MAAM,CAgBlB;IAED,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/jserrors/aggregate/index.js"],"names":[],"mappings":"AA2BA;;GAEG;AAEH;IACE,2BAAiC;IACjC,mDAgCC;IA7BC,kBAAuB;IACvB,eAAoB;IACpB,qBAA0B;IAC1B,2BAAgC;IAChC,uCAA4B;IAC5B,qBAAwB;IA0B1B;;;MA2BC;IAED,qCAWC;IAED,8BAEC;IAED,oEAMC;IAED;;;;;;OAMG;IACH,qCAHW,SAAS,GACP,MAAM,CAgBlB;IAED,4FA4FC;IA4BD,yDA6BC;IAED,qFAOC;;CAwBF;wBA1SY,OAAO,0BAA0B,EAAE,SAAS;8BAP3B,4BAA4B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/logging/aggregate/index.js"],"names":[],"mappings":"AAiBA;IACE,2BAAiC;IAGjC,mDAwBC;IArBC,+BAA+B;IAC/B,0BAAqC;IAKrC,wBAAmG;IAGjG,4BAKQ;IASZ,+
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/logging/aggregate/index.js"],"names":[],"mappings":"AAiBA;IACE,2BAAiC;IAGjC,mDAwBC;IArBC,+BAA+B;IAC/B,0BAAqC;IAKrC,wBAAmG;IAGjG,4BAKQ;IASZ,+EA6CC;IAED;;;;;;gBASQ,0FAA0F;;;;;;;;;;;;YAY5F,0DAA0D;;;kBAY/D;IAED,qCAGC;;CACF;8BA5H6B,4BAA4B;4BAM9B,0BAA0B;iCAVrB,2CAA2C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/metrics/aggregate/index.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/metrics/aggregate/index.js"],"names":[],"mappings":"AAgBA;IACE,2BAAiC;IACjC,mDAsBC;IAED,wDAKC;IAED,iDAKC;IAED,qBAqEC;IAED,0BAOC;IAED,eAkCC;IA/BG,mCAAyB;CAgC9B;8BAjK6B,4BAA4B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/page_view_event/aggregate/index.js"],"names":[],"mappings":"AAkBA;IACE,2BAA2C;IAC3C,mDA0BC;IAvBC,wBAAwB;IACxB,8BAA8B;IAC9B,8BAA8B;IAC9B,uBAAsD;IAsBxD,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/page_view_event/aggregate/index.js"],"names":[],"mappings":"AAkBA;IACE,2BAA2C;IAC3C,mDA0BC;IAvBC,wBAAwB;IACxB,8BAA8B;IAC9B,8BAA8B;IAC9B,uBAAsD;IAsBxD,gBAiGC;CACF;8BAxI6B,4BAA4B;2BAK/B,oCAAoC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/aggregate/index.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/aggregate/index.js"],"names":[],"mappings":"AAgCA;IACE,2BAAiC;IAIjC,8DAsGC;IAzGD,aAAe;IAKb,8GAA8G;IAC9G,wBAAgH;IAChH,iFAAiF;IACjF,qBAAwB;IAGxB,2CAA2C;IAC3C,sDAAwB;IACxB,6CAA6C;IAC7C,gDAAmB;IAEnB,0BAA0B;IAC1B,kBAAqB;IACrB,6CAA6C;IAC7C,gBAA2B;IAE3B,cAA8B;IAC9B,kBAA+C;IAoC/C,4BAKQ;IA4CV,0BAMC;IAED,qBAWC;IAED;;;;;;;OAOG;IACH,gDAHW,OAAO,GACL,IAAI,CA8DhB;IAED,2BASC;IAED;;;;;;;;;;;;oBAiDC;IAED,sCAIC;IAED;;;;;;;;;;MAwEC;IAED,qCAOC;IAED;;;;OAIG;IACH,mCAKC;IAED,yDAAyD;IACzD,+CAUC;IAED,yCAGC;CACF;8BA9Y6B,4BAA4B;iCALzB,2CAA2C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_trace/aggregate/index.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_trace/aggregate/index.js"],"names":[],"mappings":"AAkBA;IACE,2BAAiC;IAEjC,mDAmBC;IAjBC,kBAA+C;IAC/C,eAAyC;IAEzC,0FAA0F;IAC1F,wBAAqB;IACrB,wBAA0G;IAC1G,0GAA0G;IAC1G,cAAyB;IACzB,mIAAmI;IACnI,uBAA0B;IAC1B,0CAA0C;IAC1C,oBAAuB;IACvB,wIAAwI;IACxI,2BAA0C;IAM5C,gLAAgL;IAChL,mEAoEC;IA/DG,iCAAuB;IACvB,yJAAyJ;IACzJ,UAAkC;IAClC,eAAuD;IAmBD,UAA4D;IASpH,wCAKQ;IA6BV,kJAAkJ;IAClJ,wBAIC;IAED,iJAAiJ;IACjJ;;;;;;;;;;MA6DC;IAED;;OAEG;IACH,qCAKC;IAED,8DAA8D;IAC9D,qBAUC;IAED,2DAA2D;IAC3D,yBAKC;CACF;8BAhN6B,4BAA4B;6BAC7B,iBAAiB;iCANb,2CAA2C"}
|
package/package.json
CHANGED
|
@@ -48,6 +48,7 @@ const model = () => {
|
|
|
48
48
|
metrics: { enabled: true, autoStart: true },
|
|
49
49
|
obfuscate: undefined,
|
|
50
50
|
page_action: { enabled: true },
|
|
51
|
+
user_actions: { enabled: true },
|
|
51
52
|
page_view_event: { enabled: true, autoStart: true },
|
|
52
53
|
page_view_timing: { enabled: true, harvestTimeSeconds: 30, autoStart: true },
|
|
53
54
|
privacy: { cookies_enabled: true }, // *cli - per discussion, default should be true
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates a CSS selector path for the given element, if possible
|
|
3
|
+
* @param {HTMLElement} elem
|
|
4
|
+
* @param {boolean} includeId
|
|
5
|
+
* @param {boolean} includeClass
|
|
6
|
+
* @returns {string|undefined}
|
|
7
|
+
*/
|
|
8
|
+
export const generateSelectorPath = (elem) => {
|
|
9
|
+
if (!elem) return
|
|
10
|
+
|
|
11
|
+
const getNthOfTypeIndex = (node) => {
|
|
12
|
+
try {
|
|
13
|
+
let i = 1
|
|
14
|
+
const { tagName } = node
|
|
15
|
+
while (node.previousElementSibling) {
|
|
16
|
+
if (node.previousElementSibling.tagName === tagName) i++
|
|
17
|
+
node = node.previousElementSibling
|
|
18
|
+
}
|
|
19
|
+
return i
|
|
20
|
+
} catch (err) {
|
|
21
|
+
// do nothing for now. An invalid child count will make the path selector not return a nth-of-type selector statement
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let pathSelector = ''
|
|
26
|
+
let index = getNthOfTypeIndex(elem)
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
while (elem?.tagName) {
|
|
30
|
+
const { id, localName } = elem
|
|
31
|
+
const selector = [
|
|
32
|
+
localName,
|
|
33
|
+
id ? `#${id}` : '',
|
|
34
|
+
pathSelector ? `>${pathSelector}` : ''
|
|
35
|
+
].join('')
|
|
36
|
+
|
|
37
|
+
pathSelector = selector
|
|
38
|
+
elem = elem.parentNode
|
|
39
|
+
}
|
|
40
|
+
} catch (err) {
|
|
41
|
+
// do nothing for now
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return pathSelector ? index ? `${pathSelector}:nth-of-type(${index})` : pathSelector : undefined
|
|
45
|
+
}
|
|
@@ -1,34 +1,9 @@
|
|
|
1
|
-
import { globalScope } from '../constants/runtime'
|
|
2
|
-
|
|
3
|
-
/*
|
|
4
|
-
* See https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#safely_detecting_option_support
|
|
5
|
-
*/
|
|
6
|
-
let passiveSupported = false
|
|
7
|
-
let signalSupported = false
|
|
8
|
-
try {
|
|
9
|
-
const options = {
|
|
10
|
-
get passive () { // this function will be called when the browser attempts to access the passive property
|
|
11
|
-
passiveSupported = true
|
|
12
|
-
return false
|
|
13
|
-
},
|
|
14
|
-
get signal () {
|
|
15
|
-
signalSupported = true
|
|
16
|
-
return false
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
globalScope.addEventListener('test', null, options)
|
|
21
|
-
globalScope.removeEventListener('test', null, options)
|
|
22
|
-
} catch (err) {}
|
|
23
|
-
|
|
24
1
|
export function eventListenerOpts (useCapture, abortSignal) {
|
|
25
|
-
return
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
: !!useCapture // mainly just IE11 doesn't support third param options under EventTarget API
|
|
2
|
+
return {
|
|
3
|
+
capture: useCapture,
|
|
4
|
+
passive: false,
|
|
5
|
+
signal: abortSignal
|
|
6
|
+
}
|
|
32
7
|
}
|
|
33
8
|
|
|
34
9
|
/** Do not use this within the worker context. */
|
|
@@ -107,6 +107,15 @@ export class TimeKeeper {
|
|
|
107
107
|
return timestamp - this.#localTimeDiff
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Corrects relative timestamp to NR server time (epoch).
|
|
112
|
+
* @param {DOMHighResTimeStamp} relativeTime
|
|
113
|
+
* @returns {number}
|
|
114
|
+
*/
|
|
115
|
+
correctRelativeTimestamp (relativeTime) {
|
|
116
|
+
return this.correctAbsoluteTimestamp(this.convertRelativeTimestamp(relativeTime))
|
|
117
|
+
}
|
|
118
|
+
|
|
110
119
|
/** Process the session entity and use the info to set the main time calculations if present */
|
|
111
120
|
processStoredDiff () {
|
|
112
121
|
if (this.#ready) return // Time diff has already been calculated
|
|
@@ -6,10 +6,10 @@ import { stringify } from '../../../common/util/stringify'
|
|
|
6
6
|
import { HarvestScheduler } from '../../../common/harvest/harvest-scheduler'
|
|
7
7
|
import { cleanURL } from '../../../common/url/clean-url'
|
|
8
8
|
import { getInfo } from '../../../common/config/info'
|
|
9
|
-
import {
|
|
9
|
+
import { getConfiguration } from '../../../common/config/init'
|
|
10
10
|
import { getRuntime } from '../../../common/config/runtime'
|
|
11
11
|
import { FEATURE_NAME } from '../constants'
|
|
12
|
-
import { isBrowserScope } from '../../../common/constants/runtime'
|
|
12
|
+
import { initialLocation, isBrowserScope } from '../../../common/constants/runtime'
|
|
13
13
|
import { AggregateBase } from '../../utils/aggregate-base'
|
|
14
14
|
import { warn } from '../../../common/util/console'
|
|
15
15
|
import { now } from '../../../common/timing/now'
|
|
@@ -19,15 +19,18 @@ import { SUPPORTABILITY_METRIC_CHANNEL } from '../../metrics/constants'
|
|
|
19
19
|
import { EventBuffer } from '../../utils/event-buffer'
|
|
20
20
|
import { applyFnToProps } from '../../../common/util/traverse'
|
|
21
21
|
import { IDEAL_PAYLOAD_SIZE } from '../../../common/constants/agent-constants'
|
|
22
|
+
import { UserActionsAggregator } from './user-actions/user-actions-aggregator'
|
|
23
|
+
import { isIFrameWindow } from '../../../common/dom/iframe'
|
|
22
24
|
|
|
23
25
|
export class Aggregate extends AggregateBase {
|
|
24
26
|
#agentRuntime
|
|
25
27
|
static featureName = FEATURE_NAME
|
|
26
28
|
constructor (agentIdentifier, aggregator) {
|
|
27
29
|
super(agentIdentifier, aggregator, FEATURE_NAME)
|
|
30
|
+
const agentInit = getConfiguration(this.agentIdentifier)
|
|
28
31
|
|
|
29
32
|
this.eventsPerHarvest = 1000
|
|
30
|
-
this.harvestTimeSeconds =
|
|
33
|
+
this.harvestTimeSeconds = agentInit.generic_events.harvestTimeSeconds
|
|
31
34
|
|
|
32
35
|
this.referrerUrl = (isBrowserScope && document.referrer) ? cleanURL(document.referrer) : undefined
|
|
33
36
|
|
|
@@ -42,18 +45,17 @@ export class Aggregate extends AggregateBase {
|
|
|
42
45
|
return
|
|
43
46
|
}
|
|
44
47
|
|
|
45
|
-
|
|
48
|
+
const preHarvestMethods = []
|
|
49
|
+
|
|
50
|
+
if (agentInit.page_action.enabled) {
|
|
46
51
|
registerHandler('api-addPageAction', (timestamp, name, attributes) => {
|
|
47
52
|
this.addEvent({
|
|
48
53
|
...attributes,
|
|
49
54
|
eventType: 'PageAction',
|
|
50
|
-
timestamp: Math.floor(this.#agentRuntime.timeKeeper.
|
|
51
|
-
this.#agentRuntime.timeKeeper.convertRelativeTimestamp(timestamp)
|
|
52
|
-
)),
|
|
55
|
+
timestamp: Math.floor(this.#agentRuntime.timeKeeper.correctRelativeTimestamp(timestamp)),
|
|
53
56
|
timeSinceLoad: timestamp / 1000,
|
|
54
57
|
actionName: name,
|
|
55
58
|
referrerUrl: this.referrerUrl,
|
|
56
|
-
currentUrl: cleanURL('' + location),
|
|
57
59
|
...(isBrowserScope && {
|
|
58
60
|
browserWidth: window.document.documentElement?.clientWidth,
|
|
59
61
|
browserHeight: window.document.documentElement?.clientHeight
|
|
@@ -62,8 +64,53 @@ export class Aggregate extends AggregateBase {
|
|
|
62
64
|
}, this.featureName, this.ee)
|
|
63
65
|
}
|
|
64
66
|
|
|
67
|
+
if (isBrowserScope && agentInit.user_actions.enabled) {
|
|
68
|
+
this.userActionAggregator = new UserActionsAggregator()
|
|
69
|
+
|
|
70
|
+
this.addUserAction = (aggregatedUserAction) => {
|
|
71
|
+
try {
|
|
72
|
+
/** The aggregator process only returns an event when it is "done" aggregating -
|
|
73
|
+
* so we still need to validate that an event was given to this method before we try to add */
|
|
74
|
+
if (aggregatedUserAction?.event) {
|
|
75
|
+
const { target, timeStamp, type } = aggregatedUserAction.event
|
|
76
|
+
this.addEvent({
|
|
77
|
+
eventType: 'UserAction',
|
|
78
|
+
timestamp: Math.floor(this.#agentRuntime.timeKeeper.correctRelativeTimestamp(timeStamp)),
|
|
79
|
+
action: type,
|
|
80
|
+
actionCount: aggregatedUserAction.count,
|
|
81
|
+
actionDuration: aggregatedUserAction.relativeMs[aggregatedUserAction.relativeMs.length - 1],
|
|
82
|
+
actionMs: aggregatedUserAction.relativeMs,
|
|
83
|
+
rageClick: aggregatedUserAction.rageClick,
|
|
84
|
+
target: aggregatedUserAction.selectorPath,
|
|
85
|
+
...(isIFrameWindow(window) && { iframe: true }),
|
|
86
|
+
...(target?.id && { targetId: target.id }),
|
|
87
|
+
...(target?.tagName && { targetTag: target.tagName }),
|
|
88
|
+
...(target?.type && { targetType: target.type }),
|
|
89
|
+
...(target?.className && { targetClass: target.className })
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
} catch (e) {
|
|
93
|
+
// do nothing for now
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
registerHandler('ua', (evt) => {
|
|
98
|
+
/** the processor will return the previously aggregated event if it has been completed by processing the current event */
|
|
99
|
+
this.addUserAction(this.userActionAggregator.process(evt))
|
|
100
|
+
}, this.featureName, this.ee)
|
|
101
|
+
|
|
102
|
+
preHarvestMethods.push((options = {}) => {
|
|
103
|
+
/** send whatever UserActions have been aggregated up to this point
|
|
104
|
+
* if we are in a final harvest. By accessing the aggregationEvent, the aggregation is then force-cleared */
|
|
105
|
+
if (options.isFinalHarvest) this.addUserAction(this.userActionAggregator.aggregationEvent)
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
|
|
65
109
|
this.harvestScheduler = new HarvestScheduler('ins', { onFinished: (...args) => this.onHarvestFinished(...args) }, this)
|
|
66
|
-
this.harvestScheduler.harvest.on('ins', (...args) =>
|
|
110
|
+
this.harvestScheduler.harvest.on('ins', (...args) => {
|
|
111
|
+
preHarvestMethods.forEach(fn => fn(...args))
|
|
112
|
+
return this.onHarvestStarted(...args)
|
|
113
|
+
})
|
|
67
114
|
this.harvestScheduler.startTimer(this.harvestTimeSeconds, 0)
|
|
68
115
|
|
|
69
116
|
this.drain()
|
|
@@ -71,6 +118,18 @@ export class Aggregate extends AggregateBase {
|
|
|
71
118
|
}
|
|
72
119
|
|
|
73
120
|
// WARNING: Insights times are in seconds. EXCEPT timestamp, which is in ms.
|
|
121
|
+
/** Some keys are set by the query params or request headers sent with the harvest and override the body values, so check those before adding new standard body values...
|
|
122
|
+
* see harvest.js#baseQueryString for more info on the query params
|
|
123
|
+
* Notably:
|
|
124
|
+
* * name: set by the `t=` query param
|
|
125
|
+
* * appId: set by the `a=` query param
|
|
126
|
+
* * standalone: set by the `sa=` query param
|
|
127
|
+
* * session: set by the `s=` query param
|
|
128
|
+
* * sessionTraceId: set by the `ptid=` query param
|
|
129
|
+
* * userAgent*: set by the userAgent header
|
|
130
|
+
* @param {object=} obj the event object for storing in the event buffer
|
|
131
|
+
* @returns void
|
|
132
|
+
*/
|
|
74
133
|
addEvent (obj = {}) {
|
|
75
134
|
if (!obj || !Object.keys(obj).length) return
|
|
76
135
|
if (!obj.eventType) {
|
|
@@ -85,11 +144,10 @@ export class Aggregate extends AggregateBase {
|
|
|
85
144
|
|
|
86
145
|
const defaultEventAttributes = {
|
|
87
146
|
/** should be overridden by the event-specific attributes, but just in case -- set it to now() */
|
|
88
|
-
timestamp: Math.floor(this.#agentRuntime.timeKeeper.
|
|
89
|
-
|
|
90
|
-
)
|
|
91
|
-
|
|
92
|
-
pageUrl: cleanURL(getRuntime(this.agentIdentifier).origin)
|
|
147
|
+
timestamp: Math.floor(this.#agentRuntime.timeKeeper.correctRelativeTimestamp(now())),
|
|
148
|
+
/** all generic events require pageUrl(s) */
|
|
149
|
+
pageUrl: cleanURL('' + initialLocation),
|
|
150
|
+
currentUrl: cleanURL('' + location)
|
|
93
151
|
}
|
|
94
152
|
|
|
95
153
|
const eventAttributes = {
|
|
@@ -121,6 +179,8 @@ export class Aggregate extends AggregateBase {
|
|
|
121
179
|
})
|
|
122
180
|
|
|
123
181
|
if (options.retry) this.events.hold()
|
|
182
|
+
else this.events.clear()
|
|
183
|
+
|
|
124
184
|
return payload
|
|
125
185
|
}
|
|
126
186
|
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { RAGE_CLICK_THRESHOLD_EVENTS, RAGE_CLICK_THRESHOLD_MS } from '../../constants'
|
|
2
|
+
|
|
3
|
+
export class AggregatedUserAction {
|
|
4
|
+
constructor (evt, selectorPath) {
|
|
5
|
+
this.event = evt
|
|
6
|
+
this.count = 1
|
|
7
|
+
this.originMs = Math.floor(evt.timeStamp)
|
|
8
|
+
this.relativeMs = [0]
|
|
9
|
+
this.selectorPath = selectorPath
|
|
10
|
+
this.rageClick = undefined
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Aggregates the count and maintains the relative MS array for matching events
|
|
15
|
+
* Will determine if a rage click was observed as part of the aggregation
|
|
16
|
+
* @param {Event} evt
|
|
17
|
+
* @returns {void}
|
|
18
|
+
*/
|
|
19
|
+
aggregate (evt) {
|
|
20
|
+
this.count++
|
|
21
|
+
this.relativeMs.push(Math.floor(evt.timeStamp - this.originMs))
|
|
22
|
+
if (this.isRageClick()) this.rageClick = true
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Determines if the current set of relative ms values constitutes a rage click
|
|
27
|
+
* @returns {boolean}
|
|
28
|
+
*/
|
|
29
|
+
isRageClick () {
|
|
30
|
+
const len = this.relativeMs.length
|
|
31
|
+
return (this.event.type === 'click' && len >= RAGE_CLICK_THRESHOLD_EVENTS && this.relativeMs[len - 1] - this.relativeMs[len - RAGE_CLICK_THRESHOLD_EVENTS] < RAGE_CLICK_THRESHOLD_MS)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { generateSelectorPath } from '../../../../common/dom/selector-path'
|
|
2
|
+
import { OBSERVED_WINDOW_EVENTS } from '../../constants'
|
|
3
|
+
import { AggregatedUserAction } from './aggregated-user-action'
|
|
4
|
+
|
|
5
|
+
export class UserActionsAggregator {
|
|
6
|
+
/** @type {AggregatedUserAction=} */
|
|
7
|
+
#aggregationEvent = undefined
|
|
8
|
+
#aggregationKey = ''
|
|
9
|
+
|
|
10
|
+
get aggregationEvent () {
|
|
11
|
+
// if this is accessed externally, we need to be done aggregating on it
|
|
12
|
+
// to prevent potential mutability and duplication issues, so the state is cleared upon returning.
|
|
13
|
+
// This value may need to be accessed during an unload harvest.
|
|
14
|
+
const finishedEvent = this.#aggregationEvent
|
|
15
|
+
this.#aggregationKey = ''
|
|
16
|
+
this.#aggregationEvent = undefined
|
|
17
|
+
return finishedEvent
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Process the event and determine if a new aggregation set should be made or if it should increment the current aggregation
|
|
22
|
+
* @param {Event} evt The event supplied by the addEventListener callback
|
|
23
|
+
* @returns {AggregatedUserAction|undefined} The previous aggregation set if it has been completed by processing the current event
|
|
24
|
+
*/
|
|
25
|
+
process (evt) {
|
|
26
|
+
if (!evt) return
|
|
27
|
+
const selectorPath = getSelectorPath(evt)
|
|
28
|
+
const aggregationKey = getAggregationKey(evt, selectorPath)
|
|
29
|
+
if (!!aggregationKey && aggregationKey === this.#aggregationKey) {
|
|
30
|
+
// an aggregation exists already, so lets just continue to increment
|
|
31
|
+
this.#aggregationEvent.aggregate(evt)
|
|
32
|
+
} else {
|
|
33
|
+
// return the prev existing one (if there is one)
|
|
34
|
+
const finishedEvent = this.#aggregationEvent
|
|
35
|
+
// then set as this new event aggregation
|
|
36
|
+
this.#aggregationKey = aggregationKey
|
|
37
|
+
this.#aggregationEvent = new AggregatedUserAction(evt, selectorPath)
|
|
38
|
+
return finishedEvent
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Generates a selector path for the event, starting with simple cases like window or document and getting more complex for dom-tree traversals as needed.
|
|
45
|
+
* Will return a random selector path value if no other path can be determined, to force the aggregator to skip aggregation for this event.
|
|
46
|
+
* @param {Event} evt
|
|
47
|
+
* @returns {string}
|
|
48
|
+
*/
|
|
49
|
+
function getSelectorPath (evt) {
|
|
50
|
+
let selectorPath
|
|
51
|
+
if (OBSERVED_WINDOW_EVENTS.includes(evt.type) || evt.target === window) selectorPath = 'window'
|
|
52
|
+
else if (evt.target === document) selectorPath = 'document'
|
|
53
|
+
// if still no selectorPath, generate one from target tree that includes elem ids
|
|
54
|
+
else selectorPath = generateSelectorPath(evt.target)
|
|
55
|
+
// if STILL no selectorPath, it will return undefined which will skip aggregation for this event
|
|
56
|
+
return selectorPath
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Returns an aggregation key based on the event type and the selector path of the event's target.
|
|
61
|
+
* Scrollend events are aggregated into one set, no matter what.
|
|
62
|
+
* @param {Event} evt
|
|
63
|
+
* @param {string} selectorPath
|
|
64
|
+
* @returns {string}
|
|
65
|
+
*/
|
|
66
|
+
function getAggregationKey (evt, selectorPath) {
|
|
67
|
+
let aggregationKey = evt.type
|
|
68
|
+
/** aggregate all scrollends into one set (if sequential), no matter what their target is
|
|
69
|
+
* the aggregation group's selector path with be reflected as the first one observed
|
|
70
|
+
* due to the way the aggregation logic works (by storing the initial value and aggregating it) */
|
|
71
|
+
if (evt.type !== 'scrollend') aggregationKey += '-' + selectorPath
|
|
72
|
+
return aggregationKey
|
|
73
|
+
}
|
|
@@ -3,3 +3,9 @@ import { FEATURE_NAMES } from '../../loaders/features/features'
|
|
|
3
3
|
export const FEATURE_NAME = FEATURE_NAMES.genericEvents
|
|
4
4
|
export const IDEAL_PAYLOAD_SIZE = 64000
|
|
5
5
|
export const MAX_PAYLOAD_SIZE = 1000000
|
|
6
|
+
|
|
7
|
+
export const OBSERVED_EVENTS = ['auxclick', 'click', 'copy', 'keydown', 'paste', 'scrollend']
|
|
8
|
+
export const OBSERVED_WINDOW_EVENTS = ['focus', 'blur']
|
|
9
|
+
|
|
10
|
+
export const RAGE_CLICK_THRESHOLD_EVENTS = 4
|
|
11
|
+
export const RAGE_CLICK_THRESHOLD_MS = 1000
|
|
@@ -2,19 +2,35 @@
|
|
|
2
2
|
* SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { getConfiguration } from '../../../common/config/init'
|
|
6
|
+
import { isBrowserScope } from '../../../common/constants/runtime'
|
|
6
7
|
import { deregisterDrain } from '../../../common/drain/drain'
|
|
8
|
+
import { handle } from '../../../common/event-emitter/handle'
|
|
9
|
+
import { windowAddEventListener } from '../../../common/event-listener/event-listener-opts'
|
|
7
10
|
import { InstrumentBase } from '../../utils/instrument-base'
|
|
8
|
-
import { FEATURE_NAME } from '../constants'
|
|
11
|
+
import { FEATURE_NAME, OBSERVED_EVENTS, OBSERVED_WINDOW_EVENTS } from '../constants'
|
|
9
12
|
|
|
10
13
|
export class Instrument extends InstrumentBase {
|
|
11
14
|
static featureName = FEATURE_NAME
|
|
12
15
|
constructor (agentIdentifier, aggregator, auto = true) {
|
|
13
16
|
super(agentIdentifier, aggregator, FEATURE_NAME, auto)
|
|
17
|
+
const agentInit = getConfiguration(this.agentIdentifier)
|
|
14
18
|
const genericEventSourceConfigs = [
|
|
15
|
-
|
|
19
|
+
agentInit.page_action.enabled,
|
|
20
|
+
agentInit.user_actions.enabled
|
|
16
21
|
// other future generic event source configs to go here, like M&Ms, PageResouce, etc.
|
|
17
22
|
]
|
|
23
|
+
|
|
24
|
+
if (isBrowserScope && agentInit.user_actions.enabled) {
|
|
25
|
+
OBSERVED_EVENTS.forEach(eventType =>
|
|
26
|
+
windowAddEventListener(eventType, (evt) => handle('ua', [evt], undefined, this.featureName, this.ee), true)
|
|
27
|
+
)
|
|
28
|
+
OBSERVED_WINDOW_EVENTS.forEach(eventType =>
|
|
29
|
+
windowAddEventListener(eventType, (evt) => handle('ua', [evt], undefined, this.featureName, this.ee))
|
|
30
|
+
// Capture is not used here so that we don't get element focus/blur events, only the window's as they do not bubble. They are also not cancellable, so no worries about being front of line.
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
18
34
|
/** If any of the sources are active, import the aggregator. otherwise deregister */
|
|
19
35
|
if (genericEventSourceConfigs.some(x => x)) this.importAggregator()
|
|
20
36
|
else deregisterDrain(this.agentIdentifier, this.featureName)
|
|
@@ -184,9 +184,7 @@ export class Aggregate extends AggregateBase {
|
|
|
184
184
|
if (!this.stackReported[bucketHash]) {
|
|
185
185
|
this.stackReported[bucketHash] = true
|
|
186
186
|
params.stack_trace = truncateSize(stackInfo.stackString)
|
|
187
|
-
this.observedAt[bucketHash] = Math.floor(agentRuntime.timeKeeper.
|
|
188
|
-
agentRuntime.timeKeeper.convertRelativeTimestamp(time)
|
|
189
|
-
))
|
|
187
|
+
this.observedAt[bucketHash] = Math.floor(agentRuntime.timeKeeper.correctRelativeTimestamp(time))
|
|
190
188
|
} else {
|
|
191
189
|
params.browser_stack_hash = stringHashCode(stackInfo.stackString)
|
|
192
190
|
}
|
|
@@ -203,9 +201,7 @@ export class Aggregate extends AggregateBase {
|
|
|
203
201
|
}
|
|
204
202
|
|
|
205
203
|
params.firstOccurrenceTimestamp = this.observedAt[bucketHash]
|
|
206
|
-
params.timestamp = Math.floor(agentRuntime.timeKeeper.
|
|
207
|
-
agentRuntime.timeKeeper.convertRelativeTimestamp(time)
|
|
208
|
-
))
|
|
204
|
+
params.timestamp = Math.floor(agentRuntime.timeKeeper.correctRelativeTimestamp(time))
|
|
209
205
|
|
|
210
206
|
var type = internal ? 'ierr' : 'err'
|
|
211
207
|
var newMetrics = { time }
|
|
@@ -70,9 +70,7 @@ export class Aggregate extends AggregateBase {
|
|
|
70
70
|
if (typeof message !== 'string' || !message) return warn(32)
|
|
71
71
|
|
|
72
72
|
const log = new Log(
|
|
73
|
-
Math.floor(this.#agentRuntime.timeKeeper.
|
|
74
|
-
this.#agentRuntime.timeKeeper.convertRelativeTimestamp(timestamp)
|
|
75
|
-
)),
|
|
73
|
+
Math.floor(this.#agentRuntime.timeKeeper.correctRelativeTimestamp(timestamp)),
|
|
76
74
|
message,
|
|
77
75
|
attributes,
|
|
78
76
|
level
|
|
@@ -10,6 +10,7 @@ import { windowAddEventListener } from '../../../common/event-listener/event-lis
|
|
|
10
10
|
import { isBrowserScope, isWorkerScope } from '../../../common/constants/runtime'
|
|
11
11
|
import { AggregateBase } from '../../utils/aggregate-base'
|
|
12
12
|
import { deregisterDrain } from '../../../common/drain/drain'
|
|
13
|
+
import { isIFrameWindow } from '../../../common/dom/iframe'
|
|
13
14
|
// import { WEBSOCKET_TAG } from '../../../common/wrap/wrap-websocket'
|
|
14
15
|
// import { handleWebsocketEvents } from './websocket-detection'
|
|
15
16
|
|
|
@@ -101,7 +102,7 @@ export class Aggregate extends AggregateBase {
|
|
|
101
102
|
if (proxy.beacon) this.storeSupportabilityMetrics('Config/BeaconUrl/Changed')
|
|
102
103
|
|
|
103
104
|
if (isBrowserScope && window.MutationObserver) {
|
|
104
|
-
if (window
|
|
105
|
+
if (isIFrameWindow(window)) { this.storeSupportabilityMetrics('Generic/Runtime/IFrame/Detected') }
|
|
105
106
|
const preExistingVideos = window.document.querySelectorAll('video').length
|
|
106
107
|
if (preExistingVideos) this.storeSupportabilityMetrics('Generic/VideoElement/Added', preExistingVideos)
|
|
107
108
|
const preExistingIframes = window.document.querySelectorAll('iframe').length
|
|
@@ -106,9 +106,7 @@ export class Aggregate extends AggregateBase {
|
|
|
106
106
|
queryParameters.fcp = firstContentfulPaint.current.value
|
|
107
107
|
|
|
108
108
|
if (this.timeKeeper?.ready) {
|
|
109
|
-
queryParameters.timestamp = Math.floor(this.timeKeeper.
|
|
110
|
-
this.timeKeeper.convertRelativeTimestamp(now())
|
|
111
|
-
))
|
|
109
|
+
queryParameters.timestamp = Math.floor(this.timeKeeper.correctRelativeTimestamp(now()))
|
|
112
110
|
}
|
|
113
111
|
|
|
114
112
|
const rumStartTime = now()
|