@newrelic/browser-agent 1.276.0 → 1.277.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 +12 -0
- package/dist/cjs/common/constants/env.cdn.js +1 -1
- package/dist/cjs/common/constants/env.npm.js +1 -1
- package/dist/cjs/features/generic_events/aggregate/index.js +14 -3
- package/dist/cjs/features/generic_events/constants.js +2 -1
- package/dist/cjs/features/soft_navigations/aggregate/index.js +28 -11
- package/dist/cjs/features/soft_navigations/aggregate/initial-page-load-interaction.js +2 -1
- package/dist/cjs/features/soft_navigations/aggregate/interaction.js +12 -12
- package/dist/cjs/features/soft_navigations/constants.js +5 -2
- package/dist/cjs/loaders/api/api-methods.js +1 -1
- package/dist/cjs/loaders/api/api.js +2 -1
- package/dist/cjs/loaders/micro-agent-base.js +10 -0
- package/dist/esm/common/constants/env.cdn.js +1 -1
- package/dist/esm/common/constants/env.npm.js +1 -1
- package/dist/esm/features/generic_events/aggregate/index.js +15 -4
- package/dist/esm/features/generic_events/constants.js +1 -0
- package/dist/esm/features/soft_navigations/aggregate/index.js +29 -12
- package/dist/esm/features/soft_navigations/aggregate/initial-page-load-interaction.js +2 -1
- package/dist/esm/features/soft_navigations/aggregate/interaction.js +13 -13
- package/dist/esm/features/soft_navigations/constants.js +4 -1
- package/dist/esm/loaders/api/api-methods.js +1 -1
- package/dist/esm/loaders/api/api.js +2 -1
- package/dist/esm/loaders/micro-agent-base.js +10 -0
- package/dist/types/features/generic_events/aggregate/index.d.ts +1 -0
- package/dist/types/features/generic_events/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/generic_events/constants.d.ts +1 -0
- package/dist/types/features/generic_events/constants.d.ts.map +1 -1
- package/dist/types/features/soft_navigations/aggregate/index.d.ts +1 -0
- package/dist/types/features/soft_navigations/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/soft_navigations/aggregate/initial-page-load-interaction.d.ts.map +1 -1
- package/dist/types/features/soft_navigations/aggregate/interaction.d.ts +3 -3
- package/dist/types/features/soft_navigations/aggregate/interaction.d.ts.map +1 -1
- package/dist/types/features/soft_navigations/constants.d.ts +1 -0
- package/dist/types/features/soft_navigations/constants.d.ts.map +1 -1
- package/dist/types/loaders/api/api.d.ts +1 -0
- package/dist/types/loaders/api/api.d.ts.map +1 -1
- package/dist/types/loaders/micro-agent-base.d.ts +7 -0
- package/dist/types/loaders/micro-agent-base.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/features/generic_events/aggregate/index.js +17 -4
- package/src/features/generic_events/constants.js +2 -0
- package/src/features/soft_navigations/aggregate/index.js +22 -11
- package/src/features/soft_navigations/aggregate/initial-page-load-interaction.js +2 -1
- package/src/features/soft_navigations/aggregate/interaction.js +13 -12
- package/src/features/soft_navigations/constants.js +3 -1
- package/src/loaders/api/api-methods.js +1 -1
- package/src/loaders/api/api.js +3 -1
- package/src/loaders/micro-agent-base.js +10 -0
|
@@ -4,7 +4,7 @@ import { generateUuid } from '../../../common/ids/unique-id';
|
|
|
4
4
|
import { addCustomAttributes, getAddStringContext, nullable, numeric } from '../../../common/serialize/bel-serializer';
|
|
5
5
|
import { now } from '../../../common/timing/now';
|
|
6
6
|
import { cleanURL } from '../../../common/url/clean-url';
|
|
7
|
-
import { NODE_TYPE, INTERACTION_STATUS, INTERACTION_TYPE, API_TRIGGER_NAME } from '../constants';
|
|
7
|
+
import { NODE_TYPE, INTERACTION_STATUS, INTERACTION_TYPE, API_TRIGGER_NAME, IPL_TRIGGER_NAME } from '../constants';
|
|
8
8
|
import { BelNode } from './bel-node';
|
|
9
9
|
|
|
10
10
|
/**
|
|
@@ -13,8 +13,6 @@ import { BelNode } from './bel-node';
|
|
|
13
13
|
export class Interaction extends BelNode {
|
|
14
14
|
id = generateUuid(); // unique id that is serialized and used to link interactions with errors
|
|
15
15
|
initialPageURL = initialLocation;
|
|
16
|
-
oldURL = '' + globalScope?.location;
|
|
17
|
-
newURL = '' + globalScope?.location;
|
|
18
16
|
customName;
|
|
19
17
|
customAttributes = {};
|
|
20
18
|
customDataByApi = {};
|
|
@@ -29,7 +27,7 @@ export class Interaction extends BelNode {
|
|
|
29
27
|
keepOpenUntilEndApi = false;
|
|
30
28
|
onDone = [];
|
|
31
29
|
cancellationTimer;
|
|
32
|
-
constructor(agentIdentifier, uiEvent, uiEventTimestamp, currentRouteKnown) {
|
|
30
|
+
constructor(agentIdentifier, uiEvent, uiEventTimestamp, currentRouteKnown, currentUrl) {
|
|
33
31
|
super(agentIdentifier);
|
|
34
32
|
this.belType = NODE_TYPE.INTERACTION;
|
|
35
33
|
this.trigger = uiEvent;
|
|
@@ -38,6 +36,7 @@ export class Interaction extends BelNode {
|
|
|
38
36
|
this.eventSubscription = new Map([['finished', []], ['cancelled', []]]);
|
|
39
37
|
this.forceSave = this.forceIgnore = false;
|
|
40
38
|
if (this.trigger === API_TRIGGER_NAME) this.createdByApi = true;
|
|
39
|
+
this.newURL = this.oldURL = currentUrl || globalScope?.location.href;
|
|
41
40
|
}
|
|
42
41
|
updateDom(timestamp) {
|
|
43
42
|
this.domTimestamp = timestamp || now(); // default timestamp should be precise for accurate isActiveDuring calculations
|
|
@@ -57,6 +56,8 @@ export class Interaction extends BelNode {
|
|
|
57
56
|
done(customEndTime) {
|
|
58
57
|
// User could've mark this interaction--regardless UI or api started--as "don't close until .end() is called on it". Only .end provides a timestamp; the default flows do not.
|
|
59
58
|
if (this.keepOpenUntilEndApi && customEndTime === undefined) return false;
|
|
59
|
+
// If interaction is already closed, this is a no-op. However, returning true lets startUIInteraction know that it CAN start a new interaction, as this one is done.
|
|
60
|
+
if (this.status !== INTERACTION_STATUS.IP) return true;
|
|
60
61
|
this.onDone.forEach(apiProvidedCb => apiProvidedCb(this.customDataByApi)); // this interaction's .save or .ignore can still be set by these user provided callbacks for example
|
|
61
62
|
|
|
62
63
|
if (this.forceIgnore) this.#cancel(); // .ignore() always has precedence over save actions
|
|
@@ -66,7 +67,6 @@ export class Interaction extends BelNode {
|
|
|
66
67
|
return true;
|
|
67
68
|
}
|
|
68
69
|
#finish(customEndTime = 0) {
|
|
69
|
-
if (this.status !== INTERACTION_STATUS.IP) return; // disallow this call if the ixn is already done aka not in-progress
|
|
70
70
|
clearTimeout(this.cancellationTimer);
|
|
71
71
|
this.end = Math.max(this.domTimestamp, this.historyTimestamp, customEndTime);
|
|
72
72
|
this.customAttributes = {
|
|
@@ -80,7 +80,6 @@ export class Interaction extends BelNode {
|
|
|
80
80
|
callbacks.forEach(fn => fn());
|
|
81
81
|
}
|
|
82
82
|
#cancel() {
|
|
83
|
-
if (this.status !== INTERACTION_STATUS.IP) return; // disallow this call if the ixn is already done aka not in-progress
|
|
84
83
|
clearTimeout(this.cancellationTimer);
|
|
85
84
|
this.status = INTERACTION_STATUS.CAN;
|
|
86
85
|
|
|
@@ -97,7 +96,7 @@ export class Interaction extends BelNode {
|
|
|
97
96
|
*/
|
|
98
97
|
isActiveDuring(timestamp) {
|
|
99
98
|
if (this.status === INTERACTION_STATUS.IP) return this.start <= timestamp;
|
|
100
|
-
return this.status === INTERACTION_STATUS.FIN && this.start <= timestamp && this.end
|
|
99
|
+
return this.status === INTERACTION_STATUS.FIN && this.start <= timestamp && this.end > timestamp;
|
|
101
100
|
}
|
|
102
101
|
|
|
103
102
|
// Following are virtual properties overridden by a subclass:
|
|
@@ -105,16 +104,17 @@ export class Interaction extends BelNode {
|
|
|
105
104
|
get firstContentfulPaint() {}
|
|
106
105
|
get navTiming() {}
|
|
107
106
|
serialize(firstStartTimeOfPayload) {
|
|
107
|
+
const isFirstIxnOfPayload = firstStartTimeOfPayload === undefined;
|
|
108
108
|
const addString = getAddStringContext(this.agentIdentifier);
|
|
109
109
|
const nodeList = [];
|
|
110
110
|
let ixnType;
|
|
111
|
-
if (this.trigger ===
|
|
111
|
+
if (this.trigger === IPL_TRIGGER_NAME) ixnType = INTERACTION_TYPE.INITIAL_PAGE_LOAD;else if (this.newURL !== this.oldURL) ixnType = INTERACTION_TYPE.ROUTE_CHANGE;else ixnType = INTERACTION_TYPE.UNSPECIFIED;
|
|
112
112
|
|
|
113
113
|
// IMPORTANT: The order in which addString is called matters and correlates to the order in which string shows up in the harvest payload. Do not re-order the following code.
|
|
114
114
|
const fields = [numeric(this.belType), 0,
|
|
115
115
|
// this will be overwritten below with number of attached nodes
|
|
116
|
-
numeric(this.start - firstStartTimeOfPayload),
|
|
117
|
-
//
|
|
116
|
+
numeric(this.start - (isFirstIxnOfPayload ? 0 : firstStartTimeOfPayload)),
|
|
117
|
+
// the very 1st ixn does not require offset so it should fallback to a 0 while rest is offset by the very 1st ixn's start
|
|
118
118
|
numeric(this.end - this.start),
|
|
119
119
|
// end -- relative to start
|
|
120
120
|
numeric(this.callbackEnd),
|
|
@@ -125,9 +125,9 @@ export class Interaction extends BelNode {
|
|
|
125
125
|
const allAttachedNodes = addCustomAttributes(this.customAttributes || {}, addString); // start with all custom attributes
|
|
126
126
|
if (getInfo(this.agentIdentifier).atts) allAttachedNodes.push('a,' + addString(getInfo(this.agentIdentifier).atts)); // add apm provided attributes
|
|
127
127
|
/* Querypack encoder+decoder quirkiness:
|
|
128
|
-
- If first ixn node of payload is being processed,
|
|
129
|
-
- Else for subsequent
|
|
130
|
-
this.children.forEach(node => allAttachedNodes.push(node.serialize(
|
|
128
|
+
- If first ixn node of payload is being processed, its children's start time must be offset by this node's start. (firstStartTime should be undefined.)
|
|
129
|
+
- Else for subsequent ixns in the same payload, we go back to using that first ixn node's start to offset their children's start. */
|
|
130
|
+
this.children.forEach(node => allAttachedNodes.push(node.serialize(isFirstIxnOfPayload ? this.start : firstStartTimeOfPayload))); // recursively add the serialized string of every child of this (ixn) bel node
|
|
131
131
|
|
|
132
132
|
fields[1] = numeric(allAttachedNodes.length);
|
|
133
133
|
nodeList.push(fields);
|
|
@@ -3,9 +3,12 @@ export const INTERACTION_TRIGGERS = ['click',
|
|
|
3
3
|
// e.g. user clicks link or the page back/forward buttons
|
|
4
4
|
'keydown',
|
|
5
5
|
// e.g. user presses left and right arrow key to switch between displayed photo gallery
|
|
6
|
-
'submit'
|
|
6
|
+
'submit',
|
|
7
|
+
// e.g. user clicks submit butotn or presses enter while editing a form field
|
|
8
|
+
'popstate' // history api is used to navigate back and forward
|
|
7
9
|
];
|
|
8
10
|
export const API_TRIGGER_NAME = 'api';
|
|
11
|
+
export const IPL_TRIGGER_NAME = 'initialPageLoad';
|
|
9
12
|
export const FEATURE_NAME = FEATURE_NAMES.softNav;
|
|
10
13
|
export const INTERACTION_TYPE = {
|
|
11
14
|
INITIAL_PAGE_LOAD: '',
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { SR_EVENT_EMITTER_TYPES } from '../../features/session_replay/constants';
|
|
2
|
-
export const apiMethods = ['setErrorHandler', 'finished', 'addToTrace', 'addRelease', 'addPageAction', 'setCurrentRouteName', 'setPageViewName', 'setCustomAttribute', 'interaction', 'noticeError', 'setUserId', 'setApplicationVersion', 'start', SR_EVENT_EMITTER_TYPES.RECORD, SR_EVENT_EMITTER_TYPES.PAUSE, 'log', 'wrapLogger'];
|
|
2
|
+
export const apiMethods = ['setErrorHandler', 'finished', 'addToTrace', 'addRelease', 'recordCustomEvent', 'addPageAction', 'setCurrentRouteName', 'setPageViewName', 'setCustomAttribute', 'interaction', 'noticeError', 'setUserId', 'setApplicationVersion', 'start', SR_EVENT_EMITTER_TYPES.RECORD, SR_EVENT_EMITTER_TYPES.PAUSE, 'log', 'wrapLogger'];
|
|
3
3
|
export const asyncApiMethods = ['setErrorHandler', 'finished', 'addToTrace', 'addRelease'];
|
|
@@ -72,6 +72,7 @@ export function setAPI(agentIdentifier, forceDrain, runSoftNavOverSpa = false) {
|
|
|
72
72
|
apiInterface[fnName] = apiCall(prefix, fnName, true, 'api');
|
|
73
73
|
});
|
|
74
74
|
apiInterface.addPageAction = apiCall(prefix, 'addPageAction', true, FEATURE_NAMES.genericEvents);
|
|
75
|
+
apiInterface.recordCustomEvent = apiCall(prefix, 'recordCustomEvent', true, FEATURE_NAMES.genericEvents);
|
|
75
76
|
apiInterface.setPageViewName = function (name, host) {
|
|
76
77
|
if (typeof name !== 'string') return;
|
|
77
78
|
if (name.charAt(0) !== '/') name = '/' + name;
|
|
@@ -190,7 +191,7 @@ export function setAPI(agentIdentifier, forceDrain, runSoftNavOverSpa = false) {
|
|
|
190
191
|
function apiCall(prefix, name, notSpa, bufferGroup) {
|
|
191
192
|
return function () {
|
|
192
193
|
handle(SUPPORTABILITY_METRIC_CHANNEL, ['API/' + name + '/called'], undefined, FEATURE_NAMES.metrics, instanceEE);
|
|
193
|
-
if (bufferGroup) handle(prefix + name, [now(), ...arguments], notSpa ? null : this, bufferGroup, instanceEE); // no bufferGroup means only the SM is emitted
|
|
194
|
+
if (bufferGroup) handle(prefix + name, [notSpa ? now() : performance.now(), ...arguments], notSpa ? null : this, bufferGroup, instanceEE); // no bufferGroup means only the SM is emitted
|
|
194
195
|
return notSpa ? undefined : this; // returns the InteractionHandle which allows these methods to be chained
|
|
195
196
|
};
|
|
196
197
|
}
|
|
@@ -27,6 +27,16 @@ export class MicroAgentBase {
|
|
|
27
27
|
return this.#callMethod('addPageAction', name, attributes);
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Records a custom event with a specified eventType and attributes.
|
|
32
|
+
* {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/recordCustomEvent/}
|
|
33
|
+
* @param {string} eventType The eventType to store the event as.
|
|
34
|
+
* @param {object} [attributes] JSON object with one or more key/value pairs. For example: {key:"value"}.
|
|
35
|
+
*/
|
|
36
|
+
recordCustomEvent(eventType, attributes) {
|
|
37
|
+
return this.#callMethod('recordCustomEvent', eventType, attributes);
|
|
38
|
+
}
|
|
39
|
+
|
|
30
40
|
/**
|
|
31
41
|
* Groups page views to help URL structure or to capture the URL's routing information.
|
|
32
42
|
* {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/setpageviewname/}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/generic_events/aggregate/index.js"],"names":[],"mappings":"AAoBA;IACE,2BAAiC;IACjC,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/generic_events/aggregate/index.js"],"names":[],"mappings":"AAoBA;IACE,2BAAiC;IACjC,2BA+KC;IA5KC,yBAA4B;IAC5B,wBAAyE;IAEzE,gCAAkG;IAuC9F,4CAAuD;IAyHzD,mCAGQ;IASZ;;;;;;;;;;;OAWG;IACH,eAHW,MAAM,YAAC,QA0CjB;IAED,qCAEC;IAED;;;MAEC;IAED,gCAEC;IAED,mCASC;CACF;8BA1Q6B,4BAA4B;sCAOpB,wCAAwC;iCAX7C,2CAA2C"}
|
|
@@ -5,6 +5,7 @@ export const OBSERVED_EVENTS: string[];
|
|
|
5
5
|
export const OBSERVED_WINDOW_EVENTS: string[];
|
|
6
6
|
export const RAGE_CLICK_THRESHOLD_EVENTS: 4;
|
|
7
7
|
export const RAGE_CLICK_THRESHOLD_MS: 1000;
|
|
8
|
+
export const RESERVED_EVENT_TYPES: string[];
|
|
8
9
|
export namespace FEATURE_FLAGS {
|
|
9
10
|
let MARKS: string;
|
|
10
11
|
let MEASURES: string;
|
|
@@ -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;AAEvC,uCAA6F;AAC7F,8CAAuD;AAEvD,4CAA4C;AAC5C,2CAA2C"}
|
|
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;AAE3C,4CAAsF"}
|
|
@@ -8,6 +8,7 @@ export class Aggregate extends AggregateBase {
|
|
|
8
8
|
initialPageLoadInteraction: InitialPageLoadInteraction;
|
|
9
9
|
latestRouteSetByApi: any;
|
|
10
10
|
interactionInProgress: Interaction | null;
|
|
11
|
+
latestHistoryUrl: any;
|
|
11
12
|
serializer(eventBuffer: any): string;
|
|
12
13
|
startUIInteraction(eventName: any, startedAt: any, sourceElem: any): void;
|
|
13
14
|
setClosureHandlers(): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/soft_navigations/aggregate/index.js"],"names":[],"mappings":"AAaA;IACE,2BAAiC;IACjC;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/soft_navigations/aggregate/index.js"],"names":[],"mappings":"AAaA;IACE,2BAAiC;IACjC;;OAyDC;IArDC,2BAAwC;IACxC,iBAA8B;IAE9B,uDAA0F;IAa1F,yBAA+B;IAC/B,0CAAiC;IACjC,sBAA4B;IAqC9B,qCAUC;IAED,0EAiBC;IAED,2BAiBC;IAED;;;;;;;OAOG;IACH,6BAHW,mBAAmB,OAqB7B;;CA6FF;8BA5O6B,4BAA4B;2CAGf,iCAAiC;4BAChD,eAAe"}
|
package/dist/types/features/soft_navigations/aggregate/initial-page-load-interaction.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"initial-page-load-interaction.d.ts","sourceRoot":"","sources":["../../../../../src/features/soft_navigations/aggregate/initial-page-load-interaction.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"initial-page-load-interaction.d.ts","sourceRoot":"","sources":["../../../../../src/features/soft_navigations/aggregate/initial-page-load-interaction.js"],"names":[],"mappings":"AAQA;IACE,kCAKC;IAED,sBAAqD;IACrD,gCAAyE;IAEzE;;;OAGG;IACH,oCA6BC;CACF;4BApD2B,eAAe"}
|
|
@@ -2,11 +2,9 @@
|
|
|
2
2
|
* link https://github.com/newrelic/nr-querypack/blob/main/schemas/bel/7.qpschema
|
|
3
3
|
**/
|
|
4
4
|
export class Interaction extends BelNode {
|
|
5
|
-
constructor(agentIdentifier: any, uiEvent: any, uiEventTimestamp: any, currentRouteKnown: any);
|
|
5
|
+
constructor(agentIdentifier: any, uiEvent: any, uiEventTimestamp: any, currentRouteKnown: any, currentUrl: any);
|
|
6
6
|
id: string;
|
|
7
7
|
initialPageURL: string;
|
|
8
|
-
oldURL: string;
|
|
9
|
-
newURL: string;
|
|
10
8
|
customName: any;
|
|
11
9
|
customAttributes: {};
|
|
12
10
|
customDataByApi: {};
|
|
@@ -27,6 +25,8 @@ export class Interaction extends BelNode {
|
|
|
27
25
|
eventSubscription: Map<string, never[]>;
|
|
28
26
|
forceSave: boolean;
|
|
29
27
|
forceIgnore: boolean;
|
|
28
|
+
newURL: any;
|
|
29
|
+
oldURL: any;
|
|
30
30
|
updateDom(timestamp: any): void;
|
|
31
31
|
updateHistory(timestamp: any, newUrl: any): void;
|
|
32
32
|
seenHistoryAndDomChange(): boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interaction.d.ts","sourceRoot":"","sources":["../../../../../src/features/soft_navigations/aggregate/interaction.js"],"names":[],"mappings":"AASA;;IAEI;AACJ;
|
|
1
|
+
{"version":3,"file":"interaction.d.ts","sourceRoot":"","sources":["../../../../../src/features/soft_navigations/aggregate/interaction.js"],"names":[],"mappings":"AASA;;IAEI;AACJ;IAkBE,gHAaC;IA9BD,WAAmB;IACnB,uBAAgC;IAChC,gBAAU;IACV,qBAAqB;IACrB,oBAAoB;IACpB,eAAS;IACT,aAAO;IACP,cAAQ;IACR,+EAA+E;IAC/E,eAA8B;IAC9B,qBAAgB;IAChB,yBAAoB;IACpB,sBAAoB;IACpB,6BAA2B;IAC3B,cAAW;IACX,uBAAiB;IAIf,gBAAoC;IACpC,aAAsB;IAEtB,cAAiC;IACjC,wCAGE;IACF,mBAAyC;IAAxB,qBAAwB;IAEzC,YAAsE;IAAxD,YAAwD;IAGxE,gCAEC;IAED,iDAGC;IAED,mCAEC;IAED,8BAIC;IAED,kCAaC;IAsBD;;;;;OAKG;IACH,0BAHW,mBAAmB,WAM7B;IAGD,uBAAoB;IACpB,iCAA8B;IAC9B,sBAAmB;IAEnB,gDA4CC;;CACF;wBAzJuB,YAAY"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../../src/features/soft_navigations/constants.js"],"names":[],"mappings":"AAEA,
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../../src/features/soft_navigations/constants.js"],"names":[],"mappings":"AAEA,4CAKC;AACD,qCAAqC;AACrC,iDAAiD;AAEjD,kCAAiD"}
|
|
@@ -9,6 +9,7 @@ export function setAPI(agentIdentifier: any, forceDrain: any, runSoftNavOverSpa?
|
|
|
9
9
|
level?: string | undefined;
|
|
10
10
|
}): void;
|
|
11
11
|
addPageAction: (...args: any[]) => any;
|
|
12
|
+
recordCustomEvent: (...args: any[]) => any;
|
|
12
13
|
setPageViewName(name: any, host: any): any;
|
|
13
14
|
setCustomAttribute(name: any, value: any, persistAttribute?: boolean): any;
|
|
14
15
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../../../src/loaders/api/api.js"],"names":[],"mappings":"AAuBA,2CAiBC;AAID
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../../../src/loaders/api/api.js"],"names":[],"mappings":"AAuBA,2CAiBC;AAID;;;;;;;;;;;;;IAmEE;;;;OAIG;qBAFQ,MAAM;IAWjB;;;;OAIG;iCAFQ,MAAM,GAAC,IAAI;;;;;EAiGvB"}
|
|
@@ -8,6 +8,13 @@ export class MicroAgentBase {
|
|
|
8
8
|
* @param {object} [attributes] JSON object with one or more key/value pairs. For example: {key:"value"}. The key is reported as its own PageAction attribute with the specified values.
|
|
9
9
|
*/
|
|
10
10
|
addPageAction(name: string, attributes?: object | undefined): any;
|
|
11
|
+
/**
|
|
12
|
+
* Records a custom event with a specified eventType and attributes.
|
|
13
|
+
* {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/recordCustomEvent/}
|
|
14
|
+
* @param {string} eventType The eventType to store the event as.
|
|
15
|
+
* @param {object} [attributes] JSON object with one or more key/value pairs. For example: {key:"value"}.
|
|
16
|
+
*/
|
|
17
|
+
recordCustomEvent(eventType: string, attributes?: object | undefined): any;
|
|
11
18
|
/**
|
|
12
19
|
* Groups page views to help URL structure or to capture the URL's routing information.
|
|
13
20
|
* {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/setpageviewname/}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"micro-agent-base.d.ts","sourceRoot":"","sources":["../../../src/loaders/micro-agent-base.js"],"names":[],"mappings":"AAGA;IAGE,sCAEC;IAJD,wBAAe;IAkBf;;;;;OAKG;IACH,oBAHW,MAAM,wCAKhB;IAED;;;;;OAKG;IACH,sBAHW,MAAM,kCAKhB;IAED;;;;;;OAMG;IACH,yBAJW,MAAM,SACN,MAAM,GAAC,MAAM,GAAC,OAAO,GAAC,IAAI,sCAKpC;IAED;;;;;OAKG;IACH,mBAHW,KAAK,GAAC,MAAM,8CAKtB;IAED;;;;OAIG;IACH,iBAFW,MAAM,GAAC,IAAI,OAIrB;IAED;;;;;;;OAOG;IACH,6BAJW,MAAM,GAAC,IAAI,OAMrB;IAED;;;;OAIG;IACH,0BAFW,CAAC,KAAK,EAAE,KAAK,GAAC,MAAM,KAAK,OAAO,GAAG;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,OAI9D;IAED;;;;;OAKG;IACH,iBAHW,MAAM,MACN,MAAM,OAIhB;IAED;;;;;MAKE;IACF,aAHW,MAAM;2BACc,MAAM;gBAAU,OAAO,GAAC,OAAO,GAAC,OAAO,GAAC,MAAM,GAAC,MAAM;wBAInF;;CACF"}
|
|
1
|
+
{"version":3,"file":"micro-agent-base.d.ts","sourceRoot":"","sources":["../../../src/loaders/micro-agent-base.js"],"names":[],"mappings":"AAGA;IAGE,sCAEC;IAJD,wBAAe;IAkBf;;;;;OAKG;IACH,oBAHW,MAAM,wCAKhB;IAED;;;;;OAKG;IACH,6BAHW,MAAM,wCAKhB;IAED;;;;;OAKG;IACH,sBAHW,MAAM,kCAKhB;IAED;;;;;;OAMG;IACH,yBAJW,MAAM,SACN,MAAM,GAAC,MAAM,GAAC,OAAO,GAAC,IAAI,sCAKpC;IAED;;;;;OAKG;IACH,mBAHW,KAAK,GAAC,MAAM,8CAKtB;IAED;;;;OAIG;IACH,iBAFW,MAAM,GAAC,IAAI,OAIrB;IAED;;;;;;;OAOG;IACH,6BAJW,MAAM,GAAC,IAAI,OAMrB;IAED;;;;OAIG;IACH,0BAFW,CAAC,KAAK,EAAE,KAAK,GAAC,MAAM,KAAK,OAAO,GAAG;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,OAI9D;IAED;;;;;OAKG;IACH,iBAHW,MAAM,MACN,MAAM,OAIhB;IAED;;;;;MAKE;IACF,aAHW,MAAM;2BACc,MAAM;gBAAU,OAAO,GAAC,OAAO,GAAC,OAAO,GAAC,MAAM,GAAC,MAAM;wBAInF;;CACF"}
|
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
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
|
-
import { FEATURE_NAME } from '../constants'
|
|
8
|
+
import { FEATURE_NAME, RESERVED_EVENT_TYPES } from '../constants'
|
|
9
9
|
import { globalScope, initialLocation, isBrowserScope } from '../../../common/constants/runtime'
|
|
10
10
|
import { AggregateBase } from '../../utils/aggregate-base'
|
|
11
11
|
import { warn } from '../../../common/util/console'
|
|
@@ -37,12 +37,21 @@ export class Aggregate extends AggregateBase {
|
|
|
37
37
|
|
|
38
38
|
this.trackSupportabilityMetrics()
|
|
39
39
|
|
|
40
|
+
registerHandler('api-recordCustomEvent', (timestamp, eventType, attributes) => {
|
|
41
|
+
if (RESERVED_EVENT_TYPES.includes(eventType)) return warn(46)
|
|
42
|
+
this.addEvent({
|
|
43
|
+
eventType,
|
|
44
|
+
timestamp: this.toEpoch(timestamp),
|
|
45
|
+
...attributes
|
|
46
|
+
})
|
|
47
|
+
}, this.featureName, this.ee)
|
|
48
|
+
|
|
40
49
|
if (agentRef.init.page_action.enabled) {
|
|
41
50
|
registerHandler('api-addPageAction', (timestamp, name, attributes) => {
|
|
42
51
|
this.addEvent({
|
|
43
52
|
...attributes,
|
|
44
53
|
eventType: 'PageAction',
|
|
45
|
-
timestamp:
|
|
54
|
+
timestamp: this.toEpoch(timestamp),
|
|
46
55
|
timeSinceLoad: timestamp / 1000,
|
|
47
56
|
actionName: name,
|
|
48
57
|
referrerUrl: this.referrerUrl,
|
|
@@ -66,7 +75,7 @@ export class Aggregate extends AggregateBase {
|
|
|
66
75
|
const { target, timeStamp, type } = aggregatedUserAction.event
|
|
67
76
|
this.addEvent({
|
|
68
77
|
eventType: 'UserAction',
|
|
69
|
-
timestamp:
|
|
78
|
+
timestamp: this.toEpoch(timeStamp),
|
|
70
79
|
action: type,
|
|
71
80
|
actionCount: aggregatedUserAction.count,
|
|
72
81
|
actionDuration: aggregatedUserAction.relativeMs[aggregatedUserAction.relativeMs.length - 1],
|
|
@@ -118,7 +127,7 @@ export class Aggregate extends AggregateBase {
|
|
|
118
127
|
handle(SUPPORTABILITY_METRIC_CHANNEL, ['Generic/Performance/' + type + '/Seen'])
|
|
119
128
|
this.addEvent({
|
|
120
129
|
eventType: 'BrowserPerformance',
|
|
121
|
-
timestamp:
|
|
130
|
+
timestamp: this.toEpoch(entry.startTime),
|
|
122
131
|
entryName: cleanURL(entry.name),
|
|
123
132
|
entryDuration: entry.duration,
|
|
124
133
|
entryType: type,
|
|
@@ -250,6 +259,10 @@ export class Aggregate extends AggregateBase {
|
|
|
250
259
|
return { ua: this.agentRef.info.userAttributes, at: this.agentRef.info.atts }
|
|
251
260
|
}
|
|
252
261
|
|
|
262
|
+
toEpoch (timestamp) {
|
|
263
|
+
return Math.floor(this.agentRef.runtime.timeKeeper.correctRelativeTimestamp(timestamp))
|
|
264
|
+
}
|
|
265
|
+
|
|
253
266
|
trackSupportabilityMetrics () {
|
|
254
267
|
/** track usage SMs to improve these experimental features */
|
|
255
268
|
const configPerfTag = 'Config/Performance/'
|
|
@@ -10,6 +10,8 @@ export const OBSERVED_WINDOW_EVENTS = ['focus', 'blur']
|
|
|
10
10
|
export const RAGE_CLICK_THRESHOLD_EVENTS = 4
|
|
11
11
|
export const RAGE_CLICK_THRESHOLD_MS = 1000
|
|
12
12
|
|
|
13
|
+
export const RESERVED_EVENT_TYPES = ['PageAction', 'UserAction', 'BrowserPerformance']
|
|
14
|
+
|
|
13
15
|
export const FEATURE_FLAGS = {
|
|
14
16
|
MARKS: 'experimental.marks',
|
|
15
17
|
MEASURES: 'experimental.measures',
|
|
@@ -6,7 +6,7 @@ import { timeToFirstByte } from '../../../common/vitals/time-to-first-byte'
|
|
|
6
6
|
import { FEATURE_NAMES, FEATURE_TO_ENDPOINT } from '../../../loaders/features/features'
|
|
7
7
|
import { SUPPORTABILITY_METRIC_CHANNEL } from '../../metrics/constants'
|
|
8
8
|
import { AggregateBase } from '../../utils/aggregate-base'
|
|
9
|
-
import { API_TRIGGER_NAME, FEATURE_NAME, INTERACTION_STATUS } from '../constants'
|
|
9
|
+
import { API_TRIGGER_NAME, FEATURE_NAME, INTERACTION_STATUS, INTERACTION_TRIGGERS, IPL_TRIGGER_NAME } from '../constants'
|
|
10
10
|
import { AjaxNode } from './ajax-node'
|
|
11
11
|
import { InitialPageLoadInteraction } from './initial-page-load-interaction'
|
|
12
12
|
import { Interaction } from './interaction'
|
|
@@ -21,18 +21,21 @@ export class Aggregate extends AggregateBase {
|
|
|
21
21
|
this.domObserver = domObserver
|
|
22
22
|
|
|
23
23
|
this.initialPageLoadInteraction = new InitialPageLoadInteraction(agentRef.agentIdentifier)
|
|
24
|
+
this.initialPageLoadInteraction.onDone.push(() => { // this ensures the .end() method also works with iPL
|
|
25
|
+
this.initialPageLoadInteraction.forceSave = true // unless forcibly ignored, iPL always finish by default
|
|
26
|
+
this.interactionsToHarvest.add(this.initialPageLoadInteraction)
|
|
27
|
+
this.initialPageLoadInteraction = null
|
|
28
|
+
})
|
|
24
29
|
timeToFirstByte.subscribe(({ attrs }) => {
|
|
25
30
|
const loadEventTime = attrs.navigationEntry.loadEventEnd
|
|
26
|
-
this.initialPageLoadInteraction.forceSave = true
|
|
27
31
|
this.initialPageLoadInteraction.done(loadEventTime)
|
|
28
|
-
this.interactionsToHarvest.add(this.initialPageLoadInteraction)
|
|
29
|
-
this.initialPageLoadInteraction = null
|
|
30
32
|
// Report metric on the initial page load time
|
|
31
33
|
handle(SUPPORTABILITY_METRIC_CHANNEL, ['SoftNav/Interaction/InitialPageLoad/Duration/Ms', Math.round(loadEventTime)], undefined, FEATURE_NAMES.metrics, this.ee)
|
|
32
34
|
})
|
|
33
35
|
|
|
34
36
|
this.latestRouteSetByApi = null
|
|
35
37
|
this.interactionInProgress = null // aside from the "page load" interaction, there can only ever be 1 ongoing at a time
|
|
38
|
+
this.latestHistoryUrl = null
|
|
36
39
|
|
|
37
40
|
this.blocked = false
|
|
38
41
|
this.waitForFlags(['spa']).then(([spaOn]) => {
|
|
@@ -53,7 +56,11 @@ export class Aggregate extends AggregateBase {
|
|
|
53
56
|
|
|
54
57
|
// By default, a complete UI driven interaction requires event -> URL change -> DOM mod in that exact order.
|
|
55
58
|
registerHandler('newUIEvent', (event) => this.startUIInteraction(event.type, Math.floor(event.timeStamp), event.target), this.featureName, this.ee)
|
|
56
|
-
registerHandler('newURL', (timestamp, url) =>
|
|
59
|
+
registerHandler('newURL', (timestamp, url) => {
|
|
60
|
+
// In the case of 'popstate' trigger, by the time the event fires, the URL has already changed, so we need to store what-will-be the *previous* URL for oldURL of next popstate ixn.
|
|
61
|
+
this.latestHistoryUrl = url
|
|
62
|
+
this.interactionInProgress?.updateHistory(timestamp, url)
|
|
63
|
+
}, this.featureName, this.ee)
|
|
57
64
|
registerHandler('newDom', timestamp => {
|
|
58
65
|
this.interactionInProgress?.updateDom(timestamp)
|
|
59
66
|
if (this.interactionInProgress?.seenHistoryAndDomChange()) this.interactionInProgress.done()
|
|
@@ -68,21 +75,23 @@ export class Aggregate extends AggregateBase {
|
|
|
68
75
|
serializer (eventBuffer) {
|
|
69
76
|
// The payload depacker takes the first ixn of a payload (if there are multiple ixns) and positively offset the subsequent ixns timestamps by that amount.
|
|
70
77
|
// In order to accurately portray the real start & end times of the 2nd & onward ixns, we hence need to negatively offset their start timestamps with that of the 1st ixn.
|
|
71
|
-
let firstIxnStartTime
|
|
78
|
+
let firstIxnStartTime
|
|
72
79
|
const serializedIxnList = []
|
|
73
80
|
for (const interaction of eventBuffer) {
|
|
74
81
|
serializedIxnList.push(interaction.serialize(firstIxnStartTime))
|
|
75
|
-
if (
|
|
82
|
+
if (firstIxnStartTime === undefined) firstIxnStartTime = Math.floor(interaction.start) // careful not to match or overwrite on 0 value!
|
|
76
83
|
}
|
|
77
84
|
return `bel.7;${serializedIxnList.join(';')}`
|
|
78
85
|
}
|
|
79
86
|
|
|
80
87
|
startUIInteraction (eventName, startedAt, sourceElem) { // this is throttled by instrumentation so that it isn't excessively called
|
|
81
88
|
if (this.interactionInProgress?.createdByApi) return // api-started interactions cannot be disrupted aka cancelled by UI events (and the vice versa applies as well)
|
|
82
|
-
if (this.interactionInProgress?.done() === false) return
|
|
89
|
+
if (this.interactionInProgress?.done() === false) return // current in-progress is blocked from closing, e.g. by 'waitForEnd' api option
|
|
90
|
+
|
|
91
|
+
const oldURL = eventName === INTERACTION_TRIGGERS[3] ? this.latestHistoryUrl : undefined // see related comment in 'newURL' handler above, 'popstate'
|
|
92
|
+
this.interactionInProgress = new Interaction(this.agentIdentifier, eventName, startedAt, this.latestRouteSetByApi, oldURL)
|
|
83
93
|
|
|
84
|
-
|
|
85
|
-
if (eventName === 'click') {
|
|
94
|
+
if (eventName === INTERACTION_TRIGGERS[0]) { // 'click'
|
|
86
95
|
const sourceElemText = getActionText(sourceElem)
|
|
87
96
|
if (sourceElemText) this.interactionInProgress.customAttributes.actionText = sourceElemText
|
|
88
97
|
}
|
|
@@ -131,7 +140,7 @@ export class Aggregate extends AggregateBase {
|
|
|
131
140
|
for (let idx = interactionsBuffer.length - 1; idx >= 0; idx--) { // reverse search for the latest completed interaction for efficiency
|
|
132
141
|
const finishedInteraction = interactionsBuffer[idx]
|
|
133
142
|
if (finishedInteraction.isActiveDuring(timestamp)) {
|
|
134
|
-
if (finishedInteraction.trigger !==
|
|
143
|
+
if (finishedInteraction.trigger !== IPL_TRIGGER_NAME) return finishedInteraction
|
|
135
144
|
// It's possible that a complete interaction occurs before page is fully loaded, so we need to consider if a route-change ixn may have overlapped this iPL
|
|
136
145
|
else saveIxn = finishedInteraction
|
|
137
146
|
}
|
|
@@ -196,9 +205,11 @@ export class Aggregate extends AggregateBase {
|
|
|
196
205
|
// In here, 'this' refers to the EventContext specific to per InteractionHandle instance spawned by each .interaction() api call.
|
|
197
206
|
// Each api call aka IH instance would therefore retain a reference to either the in-progress interaction *at the time of the call* OR a new api-started interaction.
|
|
198
207
|
this.associatedInteraction = thisClass.getInteractionFor(time)
|
|
208
|
+
if (this.associatedInteraction?.trigger === IPL_TRIGGER_NAME) this.associatedInteraction = null // the api get-interaction method cannot target IPL
|
|
199
209
|
if (!this.associatedInteraction) {
|
|
200
210
|
// This new api-driven interaction will be the target of any subsequent .interaction() call, until it is closed by EITHER .end() OR the regular seenHistoryAndDomChange process.
|
|
201
211
|
this.associatedInteraction = thisClass.interactionInProgress = new Interaction(thisClass.agentIdentifier, API_TRIGGER_NAME, time, thisClass.latestRouteSetByApi)
|
|
212
|
+
thisClass.domObserver.observe(document.body, { attributes: true, childList: true, subtree: true, characterData: true }) // start observing for DOM changes like a regular UI-driven interaction
|
|
202
213
|
thisClass.setClosureHandlers()
|
|
203
214
|
}
|
|
204
215
|
if (waitForEnd === true) this.associatedInteraction.keepOpenUntilEndApi = true
|
|
@@ -4,10 +4,11 @@ import { numeric } from '../../../common/serialize/bel-serializer'
|
|
|
4
4
|
import { firstPaint } from '../../../common/vitals/first-paint'
|
|
5
5
|
import { firstContentfulPaint } from '../../../common/vitals/first-contentful-paint'
|
|
6
6
|
import { getInfo } from '../../../common/config/info'
|
|
7
|
+
import { IPL_TRIGGER_NAME } from '../constants'
|
|
7
8
|
|
|
8
9
|
export class InitialPageLoadInteraction extends Interaction {
|
|
9
10
|
constructor (agentIdentifier) {
|
|
10
|
-
super(agentIdentifier,
|
|
11
|
+
super(agentIdentifier, IPL_TRIGGER_NAME, 0, null)
|
|
11
12
|
const agentInfo = getInfo(agentIdentifier)
|
|
12
13
|
this.queueTime = agentInfo.queueTime
|
|
13
14
|
this.appTime = agentInfo.applicationTime
|
|
@@ -4,7 +4,7 @@ import { generateUuid } from '../../../common/ids/unique-id'
|
|
|
4
4
|
import { addCustomAttributes, getAddStringContext, nullable, numeric } from '../../../common/serialize/bel-serializer'
|
|
5
5
|
import { now } from '../../../common/timing/now'
|
|
6
6
|
import { cleanURL } from '../../../common/url/clean-url'
|
|
7
|
-
import { NODE_TYPE, INTERACTION_STATUS, INTERACTION_TYPE, API_TRIGGER_NAME } from '../constants'
|
|
7
|
+
import { NODE_TYPE, INTERACTION_STATUS, INTERACTION_TYPE, API_TRIGGER_NAME, IPL_TRIGGER_NAME } from '../constants'
|
|
8
8
|
import { BelNode } from './bel-node'
|
|
9
9
|
|
|
10
10
|
/**
|
|
@@ -13,8 +13,6 @@ import { BelNode } from './bel-node'
|
|
|
13
13
|
export class Interaction extends BelNode {
|
|
14
14
|
id = generateUuid() // unique id that is serialized and used to link interactions with errors
|
|
15
15
|
initialPageURL = initialLocation
|
|
16
|
-
oldURL = '' + globalScope?.location
|
|
17
|
-
newURL = '' + globalScope?.location
|
|
18
16
|
customName
|
|
19
17
|
customAttributes = {}
|
|
20
18
|
customDataByApi = {}
|
|
@@ -30,7 +28,7 @@ export class Interaction extends BelNode {
|
|
|
30
28
|
onDone = []
|
|
31
29
|
cancellationTimer
|
|
32
30
|
|
|
33
|
-
constructor (agentIdentifier, uiEvent, uiEventTimestamp, currentRouteKnown) {
|
|
31
|
+
constructor (agentIdentifier, uiEvent, uiEventTimestamp, currentRouteKnown, currentUrl) {
|
|
34
32
|
super(agentIdentifier)
|
|
35
33
|
this.belType = NODE_TYPE.INTERACTION
|
|
36
34
|
this.trigger = uiEvent
|
|
@@ -42,6 +40,7 @@ export class Interaction extends BelNode {
|
|
|
42
40
|
])
|
|
43
41
|
this.forceSave = this.forceIgnore = false
|
|
44
42
|
if (this.trigger === API_TRIGGER_NAME) this.createdByApi = true
|
|
43
|
+
this.newURL = this.oldURL = (currentUrl || globalScope?.location.href)
|
|
45
44
|
}
|
|
46
45
|
|
|
47
46
|
updateDom (timestamp) {
|
|
@@ -66,6 +65,9 @@ export class Interaction extends BelNode {
|
|
|
66
65
|
done (customEndTime) {
|
|
67
66
|
// User could've mark this interaction--regardless UI or api started--as "don't close until .end() is called on it". Only .end provides a timestamp; the default flows do not.
|
|
68
67
|
if (this.keepOpenUntilEndApi && customEndTime === undefined) return false
|
|
68
|
+
// If interaction is already closed, this is a no-op. However, returning true lets startUIInteraction know that it CAN start a new interaction, as this one is done.
|
|
69
|
+
if (this.status !== INTERACTION_STATUS.IP) return true
|
|
70
|
+
|
|
69
71
|
this.onDone.forEach(apiProvidedCb => apiProvidedCb(this.customDataByApi)) // this interaction's .save or .ignore can still be set by these user provided callbacks for example
|
|
70
72
|
|
|
71
73
|
if (this.forceIgnore) this.#cancel() // .ignore() always has precedence over save actions
|
|
@@ -76,7 +78,6 @@ export class Interaction extends BelNode {
|
|
|
76
78
|
}
|
|
77
79
|
|
|
78
80
|
#finish (customEndTime = 0) {
|
|
79
|
-
if (this.status !== INTERACTION_STATUS.IP) return // disallow this call if the ixn is already done aka not in-progress
|
|
80
81
|
clearTimeout(this.cancellationTimer)
|
|
81
82
|
this.end = Math.max(this.domTimestamp, this.historyTimestamp, customEndTime)
|
|
82
83
|
this.customAttributes = { ...getInfo(this.agentIdentifier).jsAttributes, ...this.customAttributes } // attrs specific to this interaction should have precedence over the general custom attrs
|
|
@@ -88,7 +89,6 @@ export class Interaction extends BelNode {
|
|
|
88
89
|
}
|
|
89
90
|
|
|
90
91
|
#cancel () {
|
|
91
|
-
if (this.status !== INTERACTION_STATUS.IP) return // disallow this call if the ixn is already done aka not in-progress
|
|
92
92
|
clearTimeout(this.cancellationTimer)
|
|
93
93
|
this.status = INTERACTION_STATUS.CAN
|
|
94
94
|
|
|
@@ -105,7 +105,7 @@ export class Interaction extends BelNode {
|
|
|
105
105
|
*/
|
|
106
106
|
isActiveDuring (timestamp) {
|
|
107
107
|
if (this.status === INTERACTION_STATUS.IP) return this.start <= timestamp
|
|
108
|
-
return (this.status === INTERACTION_STATUS.FIN && this.start <= timestamp && this.end
|
|
108
|
+
return (this.status === INTERACTION_STATUS.FIN && this.start <= timestamp && this.end > timestamp)
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
// Following are virtual properties overridden by a subclass:
|
|
@@ -114,10 +114,11 @@ export class Interaction extends BelNode {
|
|
|
114
114
|
get navTiming () {}
|
|
115
115
|
|
|
116
116
|
serialize (firstStartTimeOfPayload) {
|
|
117
|
+
const isFirstIxnOfPayload = firstStartTimeOfPayload === undefined
|
|
117
118
|
const addString = getAddStringContext(this.agentIdentifier)
|
|
118
119
|
const nodeList = []
|
|
119
120
|
let ixnType
|
|
120
|
-
if (this.trigger ===
|
|
121
|
+
if (this.trigger === IPL_TRIGGER_NAME) ixnType = INTERACTION_TYPE.INITIAL_PAGE_LOAD
|
|
121
122
|
else if (this.newURL !== this.oldURL) ixnType = INTERACTION_TYPE.ROUTE_CHANGE
|
|
122
123
|
else ixnType = INTERACTION_TYPE.UNSPECIFIED
|
|
123
124
|
|
|
@@ -125,7 +126,7 @@ export class Interaction extends BelNode {
|
|
|
125
126
|
const fields = [
|
|
126
127
|
numeric(this.belType),
|
|
127
128
|
0, // this will be overwritten below with number of attached nodes
|
|
128
|
-
numeric(this.start - firstStartTimeOfPayload), //
|
|
129
|
+
numeric(this.start - (isFirstIxnOfPayload ? 0 : firstStartTimeOfPayload)), // the very 1st ixn does not require offset so it should fallback to a 0 while rest is offset by the very 1st ixn's start
|
|
129
130
|
numeric(this.end - this.start), // end -- relative to start
|
|
130
131
|
numeric(this.callbackEnd), // cbEnd -- relative to start; not used by BrowserInteraction events
|
|
131
132
|
numeric(this.callbackDuration), // not relative
|
|
@@ -144,9 +145,9 @@ export class Interaction extends BelNode {
|
|
|
144
145
|
const allAttachedNodes = addCustomAttributes(this.customAttributes || {}, addString) // start with all custom attributes
|
|
145
146
|
if (getInfo(this.agentIdentifier).atts) allAttachedNodes.push('a,' + addString(getInfo(this.agentIdentifier).atts)) // add apm provided attributes
|
|
146
147
|
/* Querypack encoder+decoder quirkiness:
|
|
147
|
-
- If first ixn node of payload is being processed,
|
|
148
|
-
- Else for subsequent
|
|
149
|
-
this.children.forEach(node => allAttachedNodes.push(node.serialize(
|
|
148
|
+
- If first ixn node of payload is being processed, its children's start time must be offset by this node's start. (firstStartTime should be undefined.)
|
|
149
|
+
- Else for subsequent ixns in the same payload, we go back to using that first ixn node's start to offset their children's start. */
|
|
150
|
+
this.children.forEach(node => allAttachedNodes.push(node.serialize(isFirstIxnOfPayload ? this.start : firstStartTimeOfPayload))) // recursively add the serialized string of every child of this (ixn) bel node
|
|
150
151
|
|
|
151
152
|
fields[1] = numeric(allAttachedNodes.length)
|
|
152
153
|
nodeList.push(fields)
|
|
@@ -3,9 +3,11 @@ import { FEATURE_NAMES } from '../../loaders/features/features'
|
|
|
3
3
|
export const INTERACTION_TRIGGERS = [
|
|
4
4
|
'click', // e.g. user clicks link or the page back/forward buttons
|
|
5
5
|
'keydown', // e.g. user presses left and right arrow key to switch between displayed photo gallery
|
|
6
|
-
'submit' // e.g. user clicks submit butotn or presses enter while editing a form field
|
|
6
|
+
'submit', // e.g. user clicks submit butotn or presses enter while editing a form field
|
|
7
|
+
'popstate' // history api is used to navigate back and forward
|
|
7
8
|
]
|
|
8
9
|
export const API_TRIGGER_NAME = 'api'
|
|
10
|
+
export const IPL_TRIGGER_NAME = 'initialPageLoad'
|
|
9
11
|
|
|
10
12
|
export const FEATURE_NAME = FEATURE_NAMES.softNav
|
|
11
13
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { SR_EVENT_EMITTER_TYPES } from '../../features/session_replay/constants'
|
|
2
2
|
|
|
3
3
|
export const apiMethods = [
|
|
4
|
-
'setErrorHandler', 'finished', 'addToTrace', 'addRelease',
|
|
4
|
+
'setErrorHandler', 'finished', 'addToTrace', 'addRelease', 'recordCustomEvent',
|
|
5
5
|
'addPageAction', 'setCurrentRouteName', 'setPageViewName', 'setCustomAttribute',
|
|
6
6
|
'interaction', 'noticeError', 'setUserId', 'setApplicationVersion', 'start',
|
|
7
7
|
SR_EVENT_EMITTER_TYPES.RECORD, SR_EVENT_EMITTER_TYPES.PAUSE, 'log', 'wrapLogger'
|