@atlaskit/insm 0.0.2 → 0.1.1
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 +13 -0
- package/README.md +203 -5
- package/dist/cjs/index.js +55 -1
- package/dist/cjs/inp-measurers/inp.js +184 -0
- package/dist/cjs/insm-period.js +345 -0
- package/dist/cjs/insm-session.js +183 -0
- package/dist/cjs/insm.js +129 -0
- package/dist/cjs/period-measurers/afps.js +193 -0
- package/dist/cjs/types.js +5 -0
- package/dist/es2019/index.js +54 -1
- package/dist/es2019/inp-measurers/inp.js +142 -0
- package/dist/es2019/insm-period.js +246 -0
- package/dist/es2019/insm-session.js +149 -0
- package/dist/es2019/insm.js +105 -0
- package/dist/es2019/period-measurers/afps.js +153 -0
- package/dist/es2019/types.js +1 -0
- package/dist/esm/index.js +54 -1
- package/dist/esm/inp-measurers/inp.js +177 -0
- package/dist/esm/insm-period.js +339 -0
- package/dist/esm/insm-session.js +177 -0
- package/dist/esm/insm.js +122 -0
- package/dist/esm/period-measurers/afps.js +186 -0
- package/dist/esm/types.js +1 -0
- package/dist/types/index.d.ts +10 -1
- package/dist/types/inp-measurers/inp.d.ts +37 -0
- package/dist/types/insm-period.d.ts +72 -0
- package/dist/types/insm-session.d.ts +91 -0
- package/dist/types/insm.d.ts +61 -0
- package/dist/types/period-measurers/afps.d.ts +57 -0
- package/dist/types/types.d.ts +81 -0
- package/dist/types-ts4.5/index.d.ts +10 -1
- package/dist/types-ts4.5/inp-measurers/inp.d.ts +37 -0
- package/dist/types-ts4.5/insm-period.d.ts +72 -0
- package/dist/types-ts4.5/insm-session.d.ts +91 -0
- package/dist/types-ts4.5/insm.d.ts +61 -0
- package/dist/types-ts4.5/period-measurers/afps.d.ts +57 -0
- package/dist/types-ts4.5/types.d.ts +81 -0
- package/package.json +6 -5
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
2
|
+
import { PeriodTracking } from './insm-period';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Only intended for internal use.
|
|
6
|
+
*
|
|
7
|
+
* Exported for consumers who may require the type.
|
|
8
|
+
*/
|
|
9
|
+
export class INSMSession {
|
|
10
|
+
constructor(experienceKey, experienceProperties, insm) {
|
|
11
|
+
_defineProperty(this, "startedAt", performance.now());
|
|
12
|
+
_defineProperty(this, "running", true);
|
|
13
|
+
_defineProperty(this, "addedProperties", []);
|
|
14
|
+
_defineProperty(this, "runningFeatures", new Set());
|
|
15
|
+
this.experienceKey = experienceKey;
|
|
16
|
+
this.experienceProperties = experienceProperties;
|
|
17
|
+
this.insm = insm;
|
|
18
|
+
this.periodTracking = new PeriodTracking(this);
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Note: Events are not reliably fired from mobile browsers (ie. when a browser is closed when not in use)
|
|
22
|
+
*/
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Adds a feature to the currently running session
|
|
27
|
+
*/
|
|
28
|
+
startFeature(featureName) {
|
|
29
|
+
var _this$periodTracking;
|
|
30
|
+
this.runningFeatures.add(featureName);
|
|
31
|
+
(_this$periodTracking = this.periodTracking) === null || _this$periodTracking === void 0 ? void 0 : _this$periodTracking.startFeature(featureName);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Ends a features usage in the currently running session
|
|
36
|
+
*/
|
|
37
|
+
endFeature(featureName) {
|
|
38
|
+
this.runningFeatures.delete(featureName);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Returns details on the current session.
|
|
43
|
+
*/
|
|
44
|
+
get details() {
|
|
45
|
+
return {
|
|
46
|
+
experienceKey: this.experienceKey,
|
|
47
|
+
experienceProperties: this.experienceProperties,
|
|
48
|
+
paused: this.periodTracking.pauses.size > 0,
|
|
49
|
+
periodState: this.periodTracking.state,
|
|
50
|
+
/**
|
|
51
|
+
* The only scenario where this value should return false is when
|
|
52
|
+
* the experience has been stopped early.
|
|
53
|
+
*/
|
|
54
|
+
running: this.running
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* This api takes either a static single-level key-value object, or callbacks which return the same and
|
|
60
|
+
* will be evaluated on session end.
|
|
61
|
+
*
|
|
62
|
+
* When ending a session, all properties received via this api are merged, in order, into the resulting
|
|
63
|
+
* insm event’s properties; last write wins.
|
|
64
|
+
*
|
|
65
|
+
* Callback values are evaluated at session end.
|
|
66
|
+
*
|
|
67
|
+
* For example, for the following
|
|
68
|
+
*
|
|
69
|
+
* ```ts
|
|
70
|
+
* insm.experience.addProperties({ one: 1, two: 2 });
|
|
71
|
+
* insm.experience.addProperties(() => ({ one: 'one' }));
|
|
72
|
+
* insm.experience.addProperties({ three: 3 });
|
|
73
|
+
* ```
|
|
74
|
+
*
|
|
75
|
+
* The resulting added properties will be
|
|
76
|
+
*
|
|
77
|
+
* ```ts
|
|
78
|
+
* { one: 'one', two: 2, three: 3 }
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
addProperties(propertiesToAdd) {
|
|
82
|
+
this.addedProperties.push(propertiesToAdd);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* In some scenarios (ie. when a page error boundary is hit), you will want to exit early.
|
|
87
|
+
* This is api supports these scenarios
|
|
88
|
+
*
|
|
89
|
+
* ```ts
|
|
90
|
+
* insm.stopEarly(reasonKey: string, description: string);
|
|
91
|
+
* ```
|
|
92
|
+
*
|
|
93
|
+
* Sessions closed early are identifiable by their end details
|
|
94
|
+
* `"endDetails": { stoppedBy: "early-stop", reasonKey, description }`.
|
|
95
|
+
*
|
|
96
|
+
* **Note**: The session is ended as soon as this is called, and any `addProperties` handlers will
|
|
97
|
+
* called immediately.
|
|
98
|
+
*/
|
|
99
|
+
earlyStop(reason, description) {
|
|
100
|
+
this.end({
|
|
101
|
+
stoppedBy: 'early-stop',
|
|
102
|
+
reason,
|
|
103
|
+
description
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
end(endDetails) {
|
|
107
|
+
var _this$insm$analyticsW;
|
|
108
|
+
if (this.running === false) {
|
|
109
|
+
// If an experience has already been ended -- don't repeat the ending
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
this.running = false;
|
|
113
|
+
const endedAt = performance.now();
|
|
114
|
+
const duration = endedAt - this.startedAt;
|
|
115
|
+
const evaluatedAddedProperties = {};
|
|
116
|
+
for (const addedProperty of this.addedProperties) {
|
|
117
|
+
if (typeof addedProperty === 'function') {
|
|
118
|
+
Object.assign(evaluatedAddedProperties, addedProperty());
|
|
119
|
+
} else {
|
|
120
|
+
Object.assign(evaluatedAddedProperties, addedProperty);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const periodResults = this.periodTracking.endResults;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* This event ends up as "insm measured"
|
|
127
|
+
*/
|
|
128
|
+
const operationalEvent = {
|
|
129
|
+
actionSubject: 'insm',
|
|
130
|
+
action: 'measured',
|
|
131
|
+
attributes: {
|
|
132
|
+
// Added first to ensure these don't overwrite any insm properties
|
|
133
|
+
...evaluatedAddedProperties,
|
|
134
|
+
experienceKey: this.experienceKey,
|
|
135
|
+
initial: this.experienceProperties.initial,
|
|
136
|
+
contentId: this.experienceProperties.contentId,
|
|
137
|
+
timing: {
|
|
138
|
+
startedAt: this.startedAt,
|
|
139
|
+
duration
|
|
140
|
+
},
|
|
141
|
+
periods: periodResults,
|
|
142
|
+
endDetails: endDetails
|
|
143
|
+
},
|
|
144
|
+
tags: ['insm'],
|
|
145
|
+
source: 'insm'
|
|
146
|
+
};
|
|
147
|
+
(_this$insm$analyticsW = this.insm.analyticsWebClient) === null || _this$insm$analyticsW === void 0 ? void 0 : _this$insm$analyticsW.sendOperationalEvent(operationalEvent);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
2
|
+
import { INSMSession } from './insm-session';
|
|
3
|
+
import { AnimationFPSIM } from './period-measurers/afps';
|
|
4
|
+
import { INPTracker } from './inp-measurers/inp';
|
|
5
|
+
export class INSM {
|
|
6
|
+
constructor(options) {
|
|
7
|
+
/**
|
|
8
|
+
* Heavy tasks are tracked at the insm layer as heavy tasks
|
|
9
|
+
* are expected at times to be unrelated to the current
|
|
10
|
+
* page session.
|
|
11
|
+
*/
|
|
12
|
+
_defineProperty(this, "runningHeavyTasks", new Set());
|
|
13
|
+
this.periodMeasurers = [new AnimationFPSIM(), new INPTracker(), new INPTracker({
|
|
14
|
+
includedInteractions: ['pointerup']
|
|
15
|
+
}), new INPTracker({
|
|
16
|
+
includedInteractions: ['pointerdown']
|
|
17
|
+
}), new INPTracker({
|
|
18
|
+
includedInteractions: ['click']
|
|
19
|
+
}), new INPTracker({
|
|
20
|
+
includedInteractions: ['keydown']
|
|
21
|
+
}), new INPTracker({
|
|
22
|
+
includedInteractions: ['keyup']
|
|
23
|
+
})];
|
|
24
|
+
this.options = options;
|
|
25
|
+
|
|
26
|
+
// If this does throw -- we do want an unhandledRejection rejection to be passed to the window
|
|
27
|
+
// this is to ease debugging.
|
|
28
|
+
options.getAnalyticsWebClient.then(analyticsWebClient => this.analyticsWebClient = analyticsWebClient);
|
|
29
|
+
|
|
30
|
+
// No cleanup needs to be performed -- as this is intended to run until the tab is closed
|
|
31
|
+
// eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
|
|
32
|
+
window.addEventListener('pagehide', () => {
|
|
33
|
+
var _this$runningSession;
|
|
34
|
+
(_this$runningSession = this.runningSession) === null || _this$runningSession === void 0 ? void 0 : _this$runningSession.end({
|
|
35
|
+
stoppedBy: 'pagehide'
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Starts a heavy task in the currently running session.
|
|
42
|
+
*
|
|
43
|
+
* This also pauses measurement.
|
|
44
|
+
*/
|
|
45
|
+
startHeavyTask(heavyTaskName) {
|
|
46
|
+
var _this$runningSession2, _this$runningSession3, _this$runningSession4;
|
|
47
|
+
this.runningHeavyTasks.add(heavyTaskName);
|
|
48
|
+
(_this$runningSession2 = this.runningSession) === null || _this$runningSession2 === void 0 ? void 0 : (_this$runningSession3 = _this$runningSession2.periodTracking) === null || _this$runningSession3 === void 0 ? void 0 : _this$runningSession3.startHeavyTask(heavyTaskName);
|
|
49
|
+
(_this$runningSession4 = this.runningSession) === null || _this$runningSession4 === void 0 ? void 0 : _this$runningSession4.periodTracking.pause(heavyTaskName);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Ends a heavy task in the currently running session
|
|
54
|
+
*/
|
|
55
|
+
endHeavyTask(heavyTaskName) {
|
|
56
|
+
var _this$runningSession5;
|
|
57
|
+
this.runningHeavyTasks.delete(heavyTaskName);
|
|
58
|
+
(_this$runningSession5 = this.runningSession) === null || _this$runningSession5 === void 0 ? void 0 : _this$runningSession5.periodTracking.resume(heavyTaskName);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Call this when starting a new experience. This is expected to be wired to the product
|
|
63
|
+
* routing solution.
|
|
64
|
+
*
|
|
65
|
+
* It's expected this call will be paired with a `insm.session.startHeavyTask('page-load')` and subsequent `insm.session.endHeavyTask('page-load')`
|
|
66
|
+
* so that performance degradations linked to the page initialisation are excluded from the active interactivity monitoring.
|
|
67
|
+
*
|
|
68
|
+
*
|
|
69
|
+
* ```ts
|
|
70
|
+
* insm.start('edit-page', { initial: true, contentId: '9001' })
|
|
71
|
+
* insm.session.startHeavyTask(''page-load')
|
|
72
|
+
* // ... heavy initialisation work
|
|
73
|
+
* insm.session.endHeavyTask(''page-load')
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
start(experienceKey, experienceProperties) {
|
|
77
|
+
var _this$options$experie;
|
|
78
|
+
if (this.runningSession !== undefined) {
|
|
79
|
+
this.runningSession.end({
|
|
80
|
+
stoppedBy: 'new-experience',
|
|
81
|
+
experienceKey: experienceKey,
|
|
82
|
+
contentId: experienceProperties.contentId
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
if ((_this$options$experie = this.options.experiences[experienceKey]) !== null && _this$options$experie !== void 0 && _this$options$experie.enabled) {
|
|
86
|
+
this.runningSession = new INSMSession(experienceKey, experienceProperties, this);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* This prematurely halts any running experience measurement. It's expected to be used in
|
|
92
|
+
* scenarios such as when error boundaries are hit.
|
|
93
|
+
*/
|
|
94
|
+
stopEarly(reasonKey, description) {
|
|
95
|
+
var _this$runningSession6;
|
|
96
|
+
(_this$runningSession6 = this.runningSession) === null || _this$runningSession6 === void 0 ? void 0 : _this$runningSession6.earlyStop(reasonKey, description);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Gets the current running session details
|
|
101
|
+
*/
|
|
102
|
+
get session() {
|
|
103
|
+
return this.runningSession;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
2
|
+
export class AnimationFPSIM {
|
|
3
|
+
constructor() {
|
|
4
|
+
/**
|
|
5
|
+
* AFPS stands for Animation Frames Per Second
|
|
6
|
+
*/
|
|
7
|
+
_defineProperty(this, "name", 'afps');
|
|
8
|
+
_defineProperty(this, "monitor", new AnimationFPSMonitor());
|
|
9
|
+
}
|
|
10
|
+
start(paused) {
|
|
11
|
+
const result = this.monitor.startNewWindow(paused);
|
|
12
|
+
return result;
|
|
13
|
+
}
|
|
14
|
+
end() {
|
|
15
|
+
const result = this.monitor.end();
|
|
16
|
+
return result;
|
|
17
|
+
}
|
|
18
|
+
pause() {
|
|
19
|
+
this.monitor.pause();
|
|
20
|
+
}
|
|
21
|
+
resume() {
|
|
22
|
+
this.monitor.resume();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
class AnimationFPSMonitor {
|
|
26
|
+
constructor() {
|
|
27
|
+
_defineProperty(this, "paused", false);
|
|
28
|
+
_defineProperty(this, "currentState", {
|
|
29
|
+
numerator: 0,
|
|
30
|
+
denominator: 0,
|
|
31
|
+
max: 0,
|
|
32
|
+
average: 0,
|
|
33
|
+
min: 0
|
|
34
|
+
});
|
|
35
|
+
// Current measurement window
|
|
36
|
+
_defineProperty(this, "windowFrameCount", 0);
|
|
37
|
+
_defineProperty(this, "windowTotalTime", 0);
|
|
38
|
+
_defineProperty(this, "currentFrameStart", 0);
|
|
39
|
+
}
|
|
40
|
+
measureWindowFPS() {
|
|
41
|
+
// Calculate FPS for this window
|
|
42
|
+
// Note: windowFrameCount and windowTotalTime is reset after each window measurement
|
|
43
|
+
const windowSeconds = this.windowTotalTime / 1000;
|
|
44
|
+
// frames / seconds
|
|
45
|
+
const windowFPS = this.windowFrameCount / windowSeconds;
|
|
46
|
+
|
|
47
|
+
// Update overall tracking
|
|
48
|
+
this.currentState.numerator += this.windowFrameCount;
|
|
49
|
+
// The denominator is seconds not ms
|
|
50
|
+
this.currentState.denominator += windowSeconds;
|
|
51
|
+
|
|
52
|
+
// Calculate overall average FPS: (total frames / total time measured in seconds
|
|
53
|
+
this.currentState.average = this.currentState.numerator / this.currentState.denominator;
|
|
54
|
+
if (this.currentState.max === 0 || windowFPS > this.currentState.max) {
|
|
55
|
+
this.currentState.max = windowFPS;
|
|
56
|
+
}
|
|
57
|
+
if (this.currentState.min === 0 || windowFPS < this.currentState.min) {
|
|
58
|
+
this.currentState.min = windowFPS;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* If there is running tracking - it will be reset
|
|
64
|
+
*/
|
|
65
|
+
startWindowTracking() {
|
|
66
|
+
// Clear any previous tracking state
|
|
67
|
+
this.resetOverallTracking();
|
|
68
|
+
const measureFrame = () => {
|
|
69
|
+
this.currentFrameStart = global.performance.now();
|
|
70
|
+
this.animationFrame = requestAnimationFrame(() => {
|
|
71
|
+
// While measurement is paused -- we don't count the animation frame towards the monitored
|
|
72
|
+
// period.
|
|
73
|
+
if (!this.paused) {
|
|
74
|
+
const frameDuration = performance.now() - this.currentFrameStart;
|
|
75
|
+
|
|
76
|
+
// Add frame to current window
|
|
77
|
+
this.windowFrameCount++;
|
|
78
|
+
this.windowTotalTime += frameDuration;
|
|
79
|
+
|
|
80
|
+
// Check if window is >= 1 second (1000ms)
|
|
81
|
+
// When the page is running smoothly it's expected this will not be hit frequently
|
|
82
|
+
if (this.windowTotalTime >= 1000) {
|
|
83
|
+
this.measureWindowFPS();
|
|
84
|
+
// Reset window for next measurement
|
|
85
|
+
this.resetWindow();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
measureFrame();
|
|
89
|
+
});
|
|
90
|
+
};
|
|
91
|
+
measureFrame();
|
|
92
|
+
}
|
|
93
|
+
startNewWindow(paused) {
|
|
94
|
+
const lastWindowResult = {
|
|
95
|
+
numerator: this.currentState.numerator,
|
|
96
|
+
denominator: this.currentState.denominator,
|
|
97
|
+
max: this.currentState.max,
|
|
98
|
+
min: this.currentState.min,
|
|
99
|
+
average: this.currentState.average
|
|
100
|
+
};
|
|
101
|
+
this.paused = paused;
|
|
102
|
+
this.startWindowTracking();
|
|
103
|
+
return lastWindowResult;
|
|
104
|
+
}
|
|
105
|
+
resetWindow() {
|
|
106
|
+
this.windowFrameCount = 0;
|
|
107
|
+
this.windowTotalTime = 0;
|
|
108
|
+
// Note this.currentFrameStart is over ridden
|
|
109
|
+
// as part of tracking - so does not need to be
|
|
110
|
+
// reset.
|
|
111
|
+
}
|
|
112
|
+
endWindowTracking() {
|
|
113
|
+
if (this.animationFrame) {
|
|
114
|
+
cancelAnimationFrame(this.animationFrame);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
resetOverallTracking() {
|
|
118
|
+
this.resetWindow();
|
|
119
|
+
this.endWindowTracking();
|
|
120
|
+
this.currentState = {
|
|
121
|
+
numerator: 0,
|
|
122
|
+
denominator: 0,
|
|
123
|
+
max: 0,
|
|
124
|
+
min: 0,
|
|
125
|
+
average: 0
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
end() {
|
|
129
|
+
try {
|
|
130
|
+
this.measureWindowFPS();
|
|
131
|
+
return this.currentState;
|
|
132
|
+
} finally {
|
|
133
|
+
this.resetOverallTracking();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
pause() {
|
|
137
|
+
// Note - we leave the tracking animationFrame monitoring running, and on resume
|
|
138
|
+
// simply set the currentFrameStart and paused to true
|
|
139
|
+
// This works because the tracking builds windows of tracked time rather than
|
|
140
|
+
// basing itself off a window start time and time elapsed time since then.
|
|
141
|
+
this.paused = true;
|
|
142
|
+
}
|
|
143
|
+
resume() {
|
|
144
|
+
// Note - We don't need to handle the gap in measurement - as the raf logic builds/increments a window time
|
|
145
|
+
// based on the currentFrameStart (and any previous measurements which can only happen while the tracking
|
|
146
|
+
// is not paused).
|
|
147
|
+
this.currentFrameStart = performance.now();
|
|
148
|
+
this.paused = false;
|
|
149
|
+
if (!this.animationFrame) {
|
|
150
|
+
this.startWindowTracking();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/esm/index.js
CHANGED
|
@@ -1,4 +1,57 @@
|
|
|
1
|
+
import { INSM } from './insm';
|
|
2
|
+
var initialisedInsm;
|
|
3
|
+
|
|
1
4
|
/**
|
|
2
5
|
* Initializes the INSM (Interactivity Session Measurement) tooling
|
|
3
6
|
*/
|
|
4
|
-
export function init() {
|
|
7
|
+
export function init(options) {
|
|
8
|
+
initialisedInsm = new INSM(options);
|
|
9
|
+
}
|
|
10
|
+
function insmInitialised() {
|
|
11
|
+
if (!initialisedInsm) {
|
|
12
|
+
// eslint-disable-next-line no-console
|
|
13
|
+
console.error('INSM used when not initialised');
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* **In**teractivity **s**ession **m**onitoring
|
|
21
|
+
*/
|
|
22
|
+
export var insm = {
|
|
23
|
+
startHeavyTask: function startHeavyTask(heavyTaskName) {
|
|
24
|
+
if (insmInitialised()) {
|
|
25
|
+
initialisedInsm.startHeavyTask(heavyTaskName);
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
endHeavyTask: function endHeavyTask(heavyTaskName) {
|
|
29
|
+
if (insmInitialised()) {
|
|
30
|
+
initialisedInsm.endHeavyTask(heavyTaskName);
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
start: function start(experienceKey, experienceProperties) {
|
|
34
|
+
if (insmInitialised()) {
|
|
35
|
+
initialisedInsm.start(experienceKey, experienceProperties);
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
stopEarly: function stopEarly(reasonKey, description) {
|
|
39
|
+
if (insmInitialised()) {
|
|
40
|
+
initialisedInsm.stopEarly(reasonKey, description);
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
// We only expose details and feature start/stop to consumers
|
|
44
|
+
// as the other properties are internals for the insm and InsmPeriod
|
|
45
|
+
// to interact with the running session.
|
|
46
|
+
get session() {
|
|
47
|
+
if (insmInitialised()) {
|
|
48
|
+
return initialisedInsm.runningSession;
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
// @ts-expect-error Private method for testing purposes
|
|
52
|
+
__setAnalyticsWebClient: function __setAnalyticsWebClient(analyticsWebClient) {
|
|
53
|
+
if (initialisedInsm) {
|
|
54
|
+
initialisedInsm.analyticsWebClient = analyticsWebClient;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
};
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
|
|
2
|
+
import _createClass from "@babel/runtime/helpers/createClass";
|
|
3
|
+
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
4
|
+
export var INPTracker = /*#__PURE__*/function () {
|
|
5
|
+
function INPTracker(options) {
|
|
6
|
+
_classCallCheck(this, INPTracker);
|
|
7
|
+
/**
|
|
8
|
+
* INP stands for Interaction to Next Paint
|
|
9
|
+
*/
|
|
10
|
+
_defineProperty(this, "name", 'inp');
|
|
11
|
+
this.includedInteractions = (options === null || options === void 0 ? void 0 : options.includedInteractions) || ['pointerdown', 'pointerup', 'click', 'keydown', 'keyup'];
|
|
12
|
+
this.monitor = new InteractionTracker(this.includedInteractions);
|
|
13
|
+
}
|
|
14
|
+
return _createClass(INPTracker, [{
|
|
15
|
+
key: "start",
|
|
16
|
+
value: function start(paused) {
|
|
17
|
+
var result = this.monitor.start(paused);
|
|
18
|
+
return result;
|
|
19
|
+
}
|
|
20
|
+
}, {
|
|
21
|
+
key: "end",
|
|
22
|
+
value: function end() {
|
|
23
|
+
var result = this.monitor.end();
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
26
|
+
}, {
|
|
27
|
+
key: "pause",
|
|
28
|
+
value: function pause() {
|
|
29
|
+
this.monitor.pause();
|
|
30
|
+
}
|
|
31
|
+
}, {
|
|
32
|
+
key: "resume",
|
|
33
|
+
value: function resume() {
|
|
34
|
+
this.monitor.resume();
|
|
35
|
+
}
|
|
36
|
+
}]);
|
|
37
|
+
}();
|
|
38
|
+
var InteractionResult = /*#__PURE__*/function () {
|
|
39
|
+
function InteractionResult() {
|
|
40
|
+
_classCallCheck(this, InteractionResult);
|
|
41
|
+
this.min = Infinity;
|
|
42
|
+
this.max = 0;
|
|
43
|
+
this.average = 0;
|
|
44
|
+
this.numerator = 0;
|
|
45
|
+
this.count = 0;
|
|
46
|
+
}
|
|
47
|
+
return _createClass(InteractionResult, [{
|
|
48
|
+
key: "update",
|
|
49
|
+
value: function update(duration) {
|
|
50
|
+
if (duration > this.max) {
|
|
51
|
+
this.max = duration;
|
|
52
|
+
}
|
|
53
|
+
if (duration < this.min) {
|
|
54
|
+
this.min = duration;
|
|
55
|
+
}
|
|
56
|
+
this.numerator += this.average * (this.count - 1) + duration;
|
|
57
|
+
this.count += 1;
|
|
58
|
+
this.average = this.numerator / this.count;
|
|
59
|
+
}
|
|
60
|
+
}, {
|
|
61
|
+
key: "toMeasure",
|
|
62
|
+
value: function toMeasure() {
|
|
63
|
+
return {
|
|
64
|
+
min: this.count === 0 ? 0 : this.min,
|
|
65
|
+
max: this.max,
|
|
66
|
+
average: this.average,
|
|
67
|
+
numerator: this.numerator,
|
|
68
|
+
denominator: this.count
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
}]);
|
|
72
|
+
}();
|
|
73
|
+
var InteractionTracker = /*#__PURE__*/function () {
|
|
74
|
+
function InteractionTracker(includedInteractions) {
|
|
75
|
+
_classCallCheck(this, InteractionTracker);
|
|
76
|
+
_defineProperty(this, "paused", false);
|
|
77
|
+
this.performanceObserver = null;
|
|
78
|
+
this.interactionResult = new InteractionResult();
|
|
79
|
+
this.includedInteractions = includedInteractions;
|
|
80
|
+
}
|
|
81
|
+
return _createClass(InteractionTracker, [{
|
|
82
|
+
key: "stopTracking",
|
|
83
|
+
value: function stopTracking() {
|
|
84
|
+
var _this$performanceObse;
|
|
85
|
+
(_this$performanceObse = this.performanceObserver) === null || _this$performanceObse === void 0 || _this$performanceObse.disconnect();
|
|
86
|
+
this.performanceObserver = null;
|
|
87
|
+
}
|
|
88
|
+
}, {
|
|
89
|
+
key: "startTracking",
|
|
90
|
+
value: function startTracking() {
|
|
91
|
+
var _this = this;
|
|
92
|
+
this.performanceObserver = new PerformanceObserver(function (list) {
|
|
93
|
+
// Note: find link to actual safari issue .. good to get rid of this if Safari has fixed this
|
|
94
|
+
// Delay by a microtask to workaround a bug in Safari where the
|
|
95
|
+
// callback is invoked immediately, rather than in a separate task.
|
|
96
|
+
// See: https://github.com/GoogleChrome/web-vitals/issues/277
|
|
97
|
+
Promise.resolve().then(function () {
|
|
98
|
+
var entries = list.getEntries();
|
|
99
|
+
entries.forEach(function (entry) {
|
|
100
|
+
// Skip further processing for entries that cannot be INP candidates.
|
|
101
|
+
//
|
|
102
|
+
// When a user interacts with a web page, a user interaction (for example a click) usually triggers a sequence of events.
|
|
103
|
+
// To measure the latency of this series of events, the events share the same interactionId.
|
|
104
|
+
// An interactionId is only computed for the following event types belonging to a user interaction. It is 0 otherwise.
|
|
105
|
+
// * click / tap / drag events: 'pointerdown', 'pointerup', 'click'
|
|
106
|
+
// * keypress events: 'keydown', 'keyup'
|
|
107
|
+
//
|
|
108
|
+
if (!entry.interactionId) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (_this.includedInteractions.includes(entry.name)) {
|
|
112
|
+
_this.interactionResult.update(entry.duration);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// Event Timing entries have their durations rounded to the nearest 8ms,
|
|
119
|
+
// so a duration of 40ms would be any event that spans 2.5 or more frames
|
|
120
|
+
// at 60Hz. This threshold is chosen to strike a balance between usefulness
|
|
121
|
+
// and performance. Running this callback for any interaction that spans
|
|
122
|
+
// just one or two frames is likely not worth the insight that could be
|
|
123
|
+
// gained.
|
|
124
|
+
if (PerformanceObserver.supportedEntryTypes.includes('event')) {
|
|
125
|
+
this.performanceObserver.observe({
|
|
126
|
+
type: 'event',
|
|
127
|
+
buffered: true,
|
|
128
|
+
durationThreshold: 40
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}, {
|
|
133
|
+
key: "reset",
|
|
134
|
+
value: function reset() {
|
|
135
|
+
this.stopTracking();
|
|
136
|
+
this.interactionResult = new InteractionResult();
|
|
137
|
+
}
|
|
138
|
+
}, {
|
|
139
|
+
key: "start",
|
|
140
|
+
value: function start(paused) {
|
|
141
|
+
var lastResult = this.interactionResult.toMeasure();
|
|
142
|
+
this.reset();
|
|
143
|
+
this.paused = paused;
|
|
144
|
+
if (!paused) {
|
|
145
|
+
this.startTracking();
|
|
146
|
+
}
|
|
147
|
+
return lastResult;
|
|
148
|
+
}
|
|
149
|
+
}, {
|
|
150
|
+
key: "end",
|
|
151
|
+
value: function end() {
|
|
152
|
+
try {
|
|
153
|
+
return this.interactionResult.toMeasure();
|
|
154
|
+
} finally {
|
|
155
|
+
this.reset();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}, {
|
|
159
|
+
key: "pause",
|
|
160
|
+
value: function pause() {
|
|
161
|
+
if (this.paused) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
this.paused = true;
|
|
165
|
+
this.stopTracking();
|
|
166
|
+
}
|
|
167
|
+
}, {
|
|
168
|
+
key: "resume",
|
|
169
|
+
value: function resume() {
|
|
170
|
+
if (!this.paused) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
this.paused = false;
|
|
174
|
+
this.startTracking();
|
|
175
|
+
}
|
|
176
|
+
}]);
|
|
177
|
+
}();
|