@atlaskit/insm 0.0.2 → 0.1.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.
Files changed (38) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +203 -5
  3. package/dist/cjs/index.js +55 -1
  4. package/dist/cjs/inp-measurers/inp.js +184 -0
  5. package/dist/cjs/insm-period.js +345 -0
  6. package/dist/cjs/insm-session.js +183 -0
  7. package/dist/cjs/insm.js +129 -0
  8. package/dist/cjs/period-measurers/afps.js +193 -0
  9. package/dist/cjs/types.js +5 -0
  10. package/dist/es2019/index.js +54 -1
  11. package/dist/es2019/inp-measurers/inp.js +142 -0
  12. package/dist/es2019/insm-period.js +246 -0
  13. package/dist/es2019/insm-session.js +149 -0
  14. package/dist/es2019/insm.js +105 -0
  15. package/dist/es2019/period-measurers/afps.js +153 -0
  16. package/dist/es2019/types.js +1 -0
  17. package/dist/esm/index.js +54 -1
  18. package/dist/esm/inp-measurers/inp.js +177 -0
  19. package/dist/esm/insm-period.js +339 -0
  20. package/dist/esm/insm-session.js +177 -0
  21. package/dist/esm/insm.js +122 -0
  22. package/dist/esm/period-measurers/afps.js +186 -0
  23. package/dist/esm/types.js +1 -0
  24. package/dist/types/index.d.ts +10 -1
  25. package/dist/types/inp-measurers/inp.d.ts +37 -0
  26. package/dist/types/insm-period.d.ts +72 -0
  27. package/dist/types/insm-session.d.ts +91 -0
  28. package/dist/types/insm.d.ts +61 -0
  29. package/dist/types/period-measurers/afps.d.ts +57 -0
  30. package/dist/types/types.d.ts +81 -0
  31. package/dist/types-ts4.5/index.d.ts +10 -1
  32. package/dist/types-ts4.5/inp-measurers/inp.d.ts +37 -0
  33. package/dist/types-ts4.5/insm-period.d.ts +72 -0
  34. package/dist/types-ts4.5/insm-session.d.ts +91 -0
  35. package/dist/types-ts4.5/insm.d.ts +61 -0
  36. package/dist/types-ts4.5/period-measurers/afps.d.ts +57 -0
  37. package/dist/types-ts4.5/types.d.ts +81 -0
  38. package/package.json +6 -5
@@ -0,0 +1,193 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.AnimationFPSIM = void 0;
8
+ var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
9
+ var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
10
+ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
11
+ var AnimationFPSIM = exports.AnimationFPSIM = /*#__PURE__*/function () {
12
+ function AnimationFPSIM() {
13
+ (0, _classCallCheck2.default)(this, AnimationFPSIM);
14
+ /**
15
+ * AFPS stands for Animation Frames Per Second
16
+ */
17
+ (0, _defineProperty2.default)(this, "name", 'afps');
18
+ (0, _defineProperty2.default)(this, "monitor", new AnimationFPSMonitor());
19
+ }
20
+ return (0, _createClass2.default)(AnimationFPSIM, [{
21
+ key: "start",
22
+ value: function start(paused) {
23
+ var result = this.monitor.startNewWindow(paused);
24
+ return result;
25
+ }
26
+ }, {
27
+ key: "end",
28
+ value: function end() {
29
+ var result = this.monitor.end();
30
+ return result;
31
+ }
32
+ }, {
33
+ key: "pause",
34
+ value: function pause() {
35
+ this.monitor.pause();
36
+ }
37
+ }, {
38
+ key: "resume",
39
+ value: function resume() {
40
+ this.monitor.resume();
41
+ }
42
+ }]);
43
+ }();
44
+ var AnimationFPSMonitor = /*#__PURE__*/function () {
45
+ function AnimationFPSMonitor() {
46
+ (0, _classCallCheck2.default)(this, AnimationFPSMonitor);
47
+ (0, _defineProperty2.default)(this, "paused", false);
48
+ (0, _defineProperty2.default)(this, "currentState", {
49
+ numerator: 0,
50
+ denominator: 0,
51
+ max: 0,
52
+ average: 0,
53
+ min: 0
54
+ });
55
+ // Current measurement window
56
+ (0, _defineProperty2.default)(this, "windowFrameCount", 0);
57
+ (0, _defineProperty2.default)(this, "windowTotalTime", 0);
58
+ (0, _defineProperty2.default)(this, "currentFrameStart", 0);
59
+ }
60
+ return (0, _createClass2.default)(AnimationFPSMonitor, [{
61
+ key: "measureWindowFPS",
62
+ value: function measureWindowFPS() {
63
+ // Calculate FPS for this window
64
+ // Note: windowFrameCount and windowTotalTime is reset after each window measurement
65
+ var windowSeconds = this.windowTotalTime / 1000;
66
+ // frames / seconds
67
+ var windowFPS = this.windowFrameCount / windowSeconds;
68
+
69
+ // Update overall tracking
70
+ this.currentState.numerator += this.windowFrameCount;
71
+ // The denominator is seconds not ms
72
+ this.currentState.denominator += windowSeconds;
73
+
74
+ // Calculate overall average FPS: (total frames / total time measured in seconds
75
+ this.currentState.average = this.currentState.numerator / this.currentState.denominator;
76
+ if (this.currentState.max === 0 || windowFPS > this.currentState.max) {
77
+ this.currentState.max = windowFPS;
78
+ }
79
+ if (this.currentState.min === 0 || windowFPS < this.currentState.min) {
80
+ this.currentState.min = windowFPS;
81
+ }
82
+ }
83
+
84
+ /**
85
+ * If there is running tracking - it will be reset
86
+ */
87
+ }, {
88
+ key: "startWindowTracking",
89
+ value: function startWindowTracking() {
90
+ var _this = this;
91
+ // Clear any previous tracking state
92
+ this.resetOverallTracking();
93
+ var _measureFrame = function measureFrame() {
94
+ _this.currentFrameStart = global.performance.now();
95
+ _this.animationFrame = requestAnimationFrame(function () {
96
+ // While measurement is paused -- we don't count the animation frame towards the monitored
97
+ // period.
98
+ if (!_this.paused) {
99
+ var frameDuration = performance.now() - _this.currentFrameStart;
100
+
101
+ // Add frame to current window
102
+ _this.windowFrameCount++;
103
+ _this.windowTotalTime += frameDuration;
104
+
105
+ // Check if window is >= 1 second (1000ms)
106
+ // When the page is running smoothly it's expected this will not be hit frequently
107
+ if (_this.windowTotalTime >= 1000) {
108
+ _this.measureWindowFPS();
109
+ // Reset window for next measurement
110
+ _this.resetWindow();
111
+ }
112
+ }
113
+ _measureFrame();
114
+ });
115
+ };
116
+ _measureFrame();
117
+ }
118
+ }, {
119
+ key: "startNewWindow",
120
+ value: function startNewWindow(paused) {
121
+ var lastWindowResult = {
122
+ numerator: this.currentState.numerator,
123
+ denominator: this.currentState.denominator,
124
+ max: this.currentState.max,
125
+ min: this.currentState.min,
126
+ average: this.currentState.average
127
+ };
128
+ this.paused = paused;
129
+ this.startWindowTracking();
130
+ return lastWindowResult;
131
+ }
132
+ }, {
133
+ key: "resetWindow",
134
+ value: function resetWindow() {
135
+ this.windowFrameCount = 0;
136
+ this.windowTotalTime = 0;
137
+ // Note this.currentFrameStart is over ridden
138
+ // as part of tracking - so does not need to be
139
+ // reset.
140
+ }
141
+ }, {
142
+ key: "endWindowTracking",
143
+ value: function endWindowTracking() {
144
+ if (this.animationFrame) {
145
+ cancelAnimationFrame(this.animationFrame);
146
+ }
147
+ }
148
+ }, {
149
+ key: "resetOverallTracking",
150
+ value: function resetOverallTracking() {
151
+ this.resetWindow();
152
+ this.endWindowTracking();
153
+ this.currentState = {
154
+ numerator: 0,
155
+ denominator: 0,
156
+ max: 0,
157
+ min: 0,
158
+ average: 0
159
+ };
160
+ }
161
+ }, {
162
+ key: "end",
163
+ value: function end() {
164
+ try {
165
+ this.measureWindowFPS();
166
+ return this.currentState;
167
+ } finally {
168
+ this.resetOverallTracking();
169
+ }
170
+ }
171
+ }, {
172
+ key: "pause",
173
+ value: function pause() {
174
+ // Note - we leave the tracking animationFrame monitoring running, and on resume
175
+ // simply set the currentFrameStart and paused to true
176
+ // This works because the tracking builds windows of tracked time rather than
177
+ // basing itself off a window start time and time elapsed time since then.
178
+ this.paused = true;
179
+ }
180
+ }, {
181
+ key: "resume",
182
+ value: function resume() {
183
+ // Note - We don't need to handle the gap in measurement - as the raf logic builds/increments a window time
184
+ // based on the currentFrameStart (and any previous measurements which can only happen while the tracking
185
+ // is not paused).
186
+ this.currentFrameStart = performance.now();
187
+ this.paused = false;
188
+ if (!this.animationFrame) {
189
+ this.startWindowTracking();
190
+ }
191
+ }
192
+ }]);
193
+ }();
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
@@ -1,4 +1,57 @@
1
+ import { INSM } from './insm';
2
+ let 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 const insm = {
23
+ startHeavyTask(heavyTaskName) {
24
+ if (insmInitialised()) {
25
+ initialisedInsm.startHeavyTask(heavyTaskName);
26
+ }
27
+ },
28
+ endHeavyTask(heavyTaskName) {
29
+ if (insmInitialised()) {
30
+ initialisedInsm.endHeavyTask(heavyTaskName);
31
+ }
32
+ },
33
+ start(experienceKey, experienceProperties) {
34
+ if (insmInitialised()) {
35
+ initialisedInsm.start(experienceKey, experienceProperties);
36
+ }
37
+ },
38
+ 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(analyticsWebClient) {
53
+ if (initialisedInsm) {
54
+ initialisedInsm.analyticsWebClient = analyticsWebClient;
55
+ }
56
+ }
57
+ };
@@ -0,0 +1,142 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+ export class INPTracker {
3
+ constructor(options) {
4
+ /**
5
+ * INP stands for Interaction to Next Paint
6
+ */
7
+ _defineProperty(this, "name", 'inp');
8
+ this.includedInteractions = (options === null || options === void 0 ? void 0 : options.includedInteractions) || ['pointerdown', 'pointerup', 'click', 'keydown', 'keyup'];
9
+ this.monitor = new InteractionTracker(this.includedInteractions);
10
+ }
11
+ start(paused) {
12
+ const result = this.monitor.start(paused);
13
+ return result;
14
+ }
15
+ end() {
16
+ const result = this.monitor.end();
17
+ return result;
18
+ }
19
+ pause() {
20
+ this.monitor.pause();
21
+ }
22
+ resume() {
23
+ this.monitor.resume();
24
+ }
25
+ }
26
+ class InteractionResult {
27
+ constructor() {
28
+ this.min = Infinity;
29
+ this.max = 0;
30
+ this.average = 0;
31
+ this.numerator = 0;
32
+ this.count = 0;
33
+ }
34
+ update(duration) {
35
+ if (duration > this.max) {
36
+ this.max = duration;
37
+ }
38
+ if (duration < this.min) {
39
+ this.min = duration;
40
+ }
41
+ this.numerator += this.average * (this.count - 1) + duration;
42
+ this.count += 1;
43
+ this.average = this.numerator / this.count;
44
+ }
45
+ toMeasure() {
46
+ return {
47
+ min: this.count === 0 ? 0 : this.min,
48
+ max: this.max,
49
+ average: this.average,
50
+ numerator: this.numerator,
51
+ denominator: this.count
52
+ };
53
+ }
54
+ }
55
+ class InteractionTracker {
56
+ constructor(includedInteractions) {
57
+ _defineProperty(this, "paused", false);
58
+ this.performanceObserver = null;
59
+ this.interactionResult = new InteractionResult();
60
+ this.includedInteractions = includedInteractions;
61
+ }
62
+ stopTracking() {
63
+ var _this$performanceObse;
64
+ (_this$performanceObse = this.performanceObserver) === null || _this$performanceObse === void 0 ? void 0 : _this$performanceObse.disconnect();
65
+ this.performanceObserver = null;
66
+ }
67
+ startTracking() {
68
+ this.performanceObserver = new PerformanceObserver(list => {
69
+ // Note: find link to actual safari issue .. good to get rid of this if Safari has fixed this
70
+ // Delay by a microtask to workaround a bug in Safari where the
71
+ // callback is invoked immediately, rather than in a separate task.
72
+ // See: https://github.com/GoogleChrome/web-vitals/issues/277
73
+ Promise.resolve().then(() => {
74
+ const entries = list.getEntries();
75
+ entries.forEach(entry => {
76
+ // Skip further processing for entries that cannot be INP candidates.
77
+ //
78
+ // When a user interacts with a web page, a user interaction (for example a click) usually triggers a sequence of events.
79
+ // To measure the latency of this series of events, the events share the same interactionId.
80
+ // An interactionId is only computed for the following event types belonging to a user interaction. It is 0 otherwise.
81
+ // * click / tap / drag events: 'pointerdown', 'pointerup', 'click'
82
+ // * keypress events: 'keydown', 'keyup'
83
+ //
84
+ if (!entry.interactionId) {
85
+ return;
86
+ }
87
+ if (this.includedInteractions.includes(entry.name)) {
88
+ this.interactionResult.update(entry.duration);
89
+ }
90
+ });
91
+ });
92
+ });
93
+
94
+ // Event Timing entries have their durations rounded to the nearest 8ms,
95
+ // so a duration of 40ms would be any event that spans 2.5 or more frames
96
+ // at 60Hz. This threshold is chosen to strike a balance between usefulness
97
+ // and performance. Running this callback for any interaction that spans
98
+ // just one or two frames is likely not worth the insight that could be
99
+ // gained.
100
+ if (PerformanceObserver.supportedEntryTypes.includes('event')) {
101
+ this.performanceObserver.observe({
102
+ type: 'event',
103
+ buffered: true,
104
+ durationThreshold: 40
105
+ });
106
+ }
107
+ }
108
+ reset() {
109
+ this.stopTracking();
110
+ this.interactionResult = new InteractionResult();
111
+ }
112
+ start(paused) {
113
+ const lastResult = this.interactionResult.toMeasure();
114
+ this.reset();
115
+ this.paused = paused;
116
+ if (!paused) {
117
+ this.startTracking();
118
+ }
119
+ return lastResult;
120
+ }
121
+ end() {
122
+ try {
123
+ return this.interactionResult.toMeasure();
124
+ } finally {
125
+ this.reset();
126
+ }
127
+ }
128
+ pause() {
129
+ if (this.paused) {
130
+ return;
131
+ }
132
+ this.paused = true;
133
+ this.stopTracking();
134
+ }
135
+ resume() {
136
+ if (!this.paused) {
137
+ return;
138
+ }
139
+ this.paused = false;
140
+ this.startTracking();
141
+ }
142
+ }
@@ -0,0 +1,246 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+ /* eslint-disable @repo/internal/dom-events/no-unsafe-event-listeners */
3
+
4
+ export class PeriodTracking {
5
+ constructor(session) {
6
+ _defineProperty(this, "periodMeasurements", {
7
+ active: {
8
+ features: new Set(),
9
+ heavyTasks: new Set(),
10
+ measurements: {},
11
+ duration: 0,
12
+ count: 0
13
+ },
14
+ inactive: {
15
+ features: new Set(),
16
+ heavyTasks: new Set(),
17
+ measurements: {},
18
+ duration: 0,
19
+ count: 0
20
+ }
21
+ });
22
+ _defineProperty(this, "state", 'inactive');
23
+ _defineProperty(this, "pauses", new Set());
24
+ /**
25
+ * Warning: this can be reset mid period when pausing/resuming.
26
+ * It's intended use is to build the `periodMeasurements` duration.
27
+ */
28
+ _defineProperty(this, "currentPeriodStart", performance.now());
29
+ _defineProperty(this, "activeStartListeners", []);
30
+ _defineProperty(this, "activeEndListeners", []);
31
+ /**
32
+ * This works by;
33
+ * On setup
34
+ * - starts activity listeners
35
+ * - on activity received
36
+ * - possible end active count down started.
37
+ * 3 animation frames or 3 seconds of inactivity - whichever comes first
38
+ * - any existing end count down ended
39
+ */
40
+ _defineProperty(this, "activeEndCountDownAbortController", new AbortController());
41
+ this.session = session;
42
+ this.latestPeriodFeatures = new Set(session.runningFeatures);
43
+ this.latestHeavyTasks = new Set(session.insm.runningHeavyTasks);
44
+ this.pauses = new Set(session.insm.runningHeavyTasks);
45
+ const startInteractivityMeasuresPaused = this.latestHeavyTasks.size !== 0;
46
+ for (const periodMeasurer of session.insm.periodMeasurers) {
47
+ periodMeasurer.start(startInteractivityMeasuresPaused);
48
+ this.periodMeasurements.active.measurements[periodMeasurer.name] = {
49
+ numerator: 0,
50
+ denominator: 0,
51
+ max: 0,
52
+ min: 0,
53
+ average: 0
54
+ };
55
+ this.periodMeasurements.inactive.measurements[periodMeasurer.name] = {
56
+ numerator: 0,
57
+ denominator: 0,
58
+ max: 0,
59
+ min: 0,
60
+ average: 0
61
+ };
62
+ }
63
+ this.setupActiveStartInteractionListeners();
64
+ }
65
+ startFeature(featureName) {
66
+ this.latestPeriodFeatures.add(featureName);
67
+ this.periodMeasurements[this.state].features.add(featureName);
68
+ }
69
+ startHeavyTask(heavyTaskName) {
70
+ this.latestHeavyTasks.add(heavyTaskName);
71
+ this.periodMeasurements[this.state].heavyTasks.add(heavyTaskName);
72
+ this.pause(heavyTaskName);
73
+ }
74
+ endHeavyTask(heavyTaskName) {
75
+ this.resume(heavyTaskName);
76
+ }
77
+
78
+ /**
79
+ * Sets a pause based on a key. If this is the first pause, then it will also halt
80
+ * running interactivity measures, and update the current measurements duration.
81
+ */
82
+ pause(pauseName) {
83
+ if (this.pauses.size === 0) {
84
+ this.periodMeasurements[this.state].duration = this.periodMeasurements[this.state].duration + (performance.now() - this.currentPeriodStart);
85
+ for (const periodMeasurer of this.session.insm.periodMeasurers) {
86
+ periodMeasurer.pause();
87
+ }
88
+ }
89
+ this.pauses.add(pauseName);
90
+ }
91
+
92
+ /**
93
+ * Releases a pause for a key.
94
+ *
95
+ * **NOTE**: The session will only resume if this was the only
96
+ * currently tracked pause key.
97
+ */
98
+ resume(pauseName) {
99
+ this.pauses.delete(pauseName);
100
+ if (this.pauses.size === 0) {
101
+ this.currentPeriodStart = performance.now();
102
+ for (const periodMeasurer of this.session.insm.periodMeasurers) {
103
+ periodMeasurer.resume();
104
+ }
105
+ }
106
+ }
107
+ get endResults() {
108
+ this.changePeriodAndTrackLast(this.state);
109
+ return this.periodMeasurements;
110
+ }
111
+ setupActiveStartInteractionListeners() {
112
+ const events = ['mousemove', 'pointerdown', 'pointerup', 'click', 'keydown', 'keyup', 'scroll'];
113
+ for (const event of events) {
114
+ const eventListener = () => {
115
+ // start event
116
+ this.changePeriodAndTrackLast('inactive', 'active');
117
+ };
118
+ window.addEventListener(event, eventListener, {
119
+ once: true
120
+ });
121
+ this.activeStartListeners.push([event, eventListener]);
122
+ }
123
+ }
124
+ setupEndActiveInteractionListeners() {
125
+ this.activeEndCountDownVisibilityListener = () => {
126
+ if (document.visibilityState === 'hidden') {
127
+ endSession();
128
+ }
129
+ };
130
+ window.addEventListener('visibilitychange', this.activeEndCountDownVisibilityListener);
131
+ const events = ['mousemove', 'pointerdown', 'pointerup', 'click', 'keydown', 'keyup', 'scroll'];
132
+ for (const event of events) {
133
+ const eventListener = () => {
134
+ this.activeEndCountDownAbortController.abort();
135
+ this.activeEndCountDownAbortController = new AbortController();
136
+ startPeriodEndCountDown(this.activeEndCountDownAbortController.signal, () => {
137
+ endSession();
138
+ });
139
+ };
140
+ window.addEventListener(event, eventListener);
141
+ this.activeEndListeners.push([event, eventListener]);
142
+ }
143
+ const endSession = () => {
144
+ this.activeEndCountDownAbortController.abort();
145
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
146
+ window.removeEventListener('visibilitychange', this.activeEndCountDownVisibilityListener);
147
+ for (const activeEndListener of this.activeEndListeners) {
148
+ window.removeEventListener(activeEndListener[0], activeEndListener[1]);
149
+ }
150
+ this.changePeriodAndTrackLast('active', 'inactive');
151
+ };
152
+
153
+ // When first setting up -- there has just been an interaction -- so we start a countdown
154
+ this.activeEndCountDownAbortController.abort();
155
+ this.activeEndCountDownAbortController = new AbortController();
156
+ startPeriodEndCountDown(this.activeEndCountDownAbortController.signal, () => {
157
+ endSession();
158
+ });
159
+ }
160
+ changePeriodAndTrackLast(lastPeriod, newPeriod) {
161
+ var _this$activeEndCountD;
162
+ this.periodMeasurements[lastPeriod].duration = this.periodMeasurements[lastPeriod].duration + (performance.now() - this.currentPeriodStart);
163
+ this.periodMeasurements[lastPeriod].count++;
164
+ const interactivityMeasuresPaused = this.latestHeavyTasks.size !== 0;
165
+ for (const interactivityMeasure of this.session.insm.periodMeasurers) {
166
+ const finalResult = newPeriod ? interactivityMeasure.start(interactivityMeasuresPaused) : interactivityMeasure.end();
167
+ const tracked = this.periodMeasurements[lastPeriod].measurements[interactivityMeasure.name];
168
+ if (finalResult.average === 0) {
169
+ // In this case -- we haven't had any measurements complete since last measurement
170
+ // so we do nothing
171
+ } else if (tracked.average === 0) {
172
+ // In this case -- we haven't had any entries come through yet - so we replace any existing entries
173
+ this.periodMeasurements[lastPeriod].measurements[interactivityMeasure.name] = finalResult;
174
+ } else {
175
+ // In this case there is a new measurement which we merge with the previous measurement
176
+ const merged = {
177
+ numerator: finalResult.numerator + tracked.numerator,
178
+ denominator: finalResult.denominator + tracked.denominator,
179
+ average: (finalResult.numerator + tracked.numerator) / (finalResult.denominator + tracked.denominator),
180
+ max: tracked.max === 0 || finalResult.max > tracked.max ? finalResult.max : tracked.max,
181
+ min: tracked.min === 0 || finalResult.min < tracked.min ? finalResult.min : tracked.min
182
+ };
183
+ this.periodMeasurements[lastPeriod].measurements[interactivityMeasure.name] = merged;
184
+ }
185
+ }
186
+ for (const listener of this.activeStartListeners) {
187
+ window.removeEventListener(listener[0], listener[1]);
188
+ }
189
+ for (const listener of this.activeEndListeners) {
190
+ window.removeEventListener(listener[0], listener[1]);
191
+ }
192
+ (_this$activeEndCountD = this.activeEndCountDownAbortController) === null || _this$activeEndCountD === void 0 ? void 0 : _this$activeEndCountD.abort();
193
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
194
+ window.removeEventListener('visibilitychange', this.activeEndCountDownVisibilityListener);
195
+ if (newPeriod) {
196
+ this.latestPeriodFeatures = new Set(this.session.runningFeatures);
197
+ this.session.runningFeatures.forEach(featureName => {
198
+ this.periodMeasurements[newPeriod].features.add(featureName);
199
+ });
200
+ this.latestHeavyTasks = new Set(this.session.insm.runningHeavyTasks);
201
+ this.currentPeriodStart = performance.now();
202
+ switch (newPeriod) {
203
+ case 'active':
204
+ this.setupEndActiveInteractionListeners();
205
+ this.state = 'active';
206
+ break;
207
+ case 'inactive':
208
+ this.setupActiveStartInteractionListeners();
209
+ this.state = 'inactive';
210
+ break;
211
+ }
212
+ }
213
+ }
214
+ }
215
+ function startPeriodEndCountDown(signal, handleEnd) {
216
+ let animationFramesSinceLastInteraction = 0;
217
+ let inactiveTimeReached = false;
218
+ let timer;
219
+ let animationFrameHandler;
220
+ const monitorAnimationFrames = () => {
221
+ animationFrameHandler = requestAnimationFrame(() => {
222
+ animationFramesSinceLastInteraction++;
223
+ if (animationFramesSinceLastInteraction < 3) {
224
+ monitorAnimationFrames();
225
+ } else {
226
+ if (inactiveTimeReached) {
227
+ handleEnd();
228
+ }
229
+ }
230
+ });
231
+ };
232
+ const monitorTime = () => {
233
+ timer = setTimeout(() => {
234
+ inactiveTimeReached = true;
235
+ if (animationFramesSinceLastInteraction === 3) {
236
+ handleEnd();
237
+ }
238
+ }, 3000);
239
+ };
240
+ monitorTime();
241
+ monitorAnimationFrames();
242
+ signal.addEventListener('abort', () => {
243
+ clearTimeout(timer);
244
+ cancelAnimationFrame(animationFrameHandler);
245
+ });
246
+ }