@atlaskit/insm 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @atlaskit/insm
2
2
 
3
+ ## 0.1.3
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies
8
+
3
9
  ## 0.1.2
4
10
 
5
11
  ### Patch Changes
package/README.md CHANGED
@@ -169,6 +169,20 @@ insm.start(experienceKey, {
169
169
  Note: calling this will end any existing experience (unless the experience key and properties
170
170
  match - in which case no session change will occur).
171
171
 
172
+ #### Updating a page session experienceKey after it's started
173
+
174
+ In some scenarios you may need to update the running sessions experience key after it has started,
175
+ in these cases you can use `overrideExperienceKey`.
176
+
177
+ If there is a running experience, it will update the running experiences name.
178
+
179
+ If there is no running experience (ie. if the original experienceKey had not been whitelisted), it
180
+ will start a new experience using the last started experienceProperties.
181
+
182
+ ```ts
183
+ insm.overrideExperienceKey('new-key');
184
+ ```
185
+
172
186
  #### Naming guidance
173
187
 
174
188
  Choose an `experienceKey` that reflects the product and content type you want to analyze.
package/dist/cjs/index.js CHANGED
@@ -16,8 +16,6 @@ function init(options) {
16
16
  }
17
17
  function insmInitialised() {
18
18
  if (!initialisedInsm) {
19
- // eslint-disable-next-line no-console
20
- console.error('INSM used when not initialised');
21
19
  return false;
22
20
  }
23
21
  return true;
@@ -42,6 +40,11 @@ var insm = exports.insm = {
42
40
  initialisedInsm.start(experienceKey, experienceProperties);
43
41
  }
44
42
  },
43
+ overrideExperienceKey: function overrideExperienceKey(experienceKey) {
44
+ if (insmInitialised()) {
45
+ initialisedInsm.overrideExperienceKey(experienceKey);
46
+ }
47
+ },
45
48
  stopEarly: function stopEarly(reasonKey, description) {
46
49
  if (insmInitialised()) {
47
50
  initialisedInsm.stopEarly(reasonKey, description);
@@ -8,7 +8,10 @@ exports.INSMSession = void 0;
8
8
  var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
9
9
  var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
10
10
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
11
+ var _bowserUltralight = _interopRequireDefault(require("bowser-ultralight"));
12
+ var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
11
13
  var _insmPeriod = require("./insm-period");
14
+ var _LongAnimationFrameMeasurer = require("./session-measurers/LongAnimationFrameMeasurer");
12
15
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
13
16
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
14
17
  function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
@@ -30,16 +33,27 @@ var INSMSession = exports.INSMSession = /*#__PURE__*/function () {
30
33
  this.experienceProperties = experienceProperties;
31
34
  this.insm = insm;
32
35
  this.periodTracking = new _insmPeriod.PeriodTracking(this);
36
+ this.longAnimationFrameMeasurer = new _LongAnimationFrameMeasurer.LongAnimationFrameMeasurer({
37
+ initial: this.experienceProperties.initial,
38
+ limit: 3,
39
+ insmSession: this,
40
+ reportingThreshold: 500
41
+ });
33
42
 
34
43
  /**
35
44
  * Note: Events are not reliably fired from mobile browsers (ie. when a browser is closed when not in use)
36
45
  */
37
46
  }
38
-
39
- /**
40
- * Adds a feature to the currently running session
41
- */
42
47
  return (0, _createClass2.default)(INSMSession, [{
48
+ key: "updateExperienceKey",
49
+ value: function updateExperienceKey(experienceKey) {
50
+ this.experienceKey = experienceKey;
51
+ }
52
+
53
+ /**
54
+ * Adds a feature to the currently running session
55
+ */
56
+ }, {
43
57
  key: "startFeature",
44
58
  value: function startFeature(featureName) {
45
59
  var _this$periodTracking;
@@ -172,13 +186,56 @@ var INSMSession = exports.INSMSession = /*#__PURE__*/function () {
172
186
  duration: duration
173
187
  },
174
188
  periods: periodResults,
175
- endDetails: endDetails
189
+ endDetails: endDetails,
190
+ longAnimationFrames: this.longAnimationFrameMeasurer.current
176
191
  }),
192
+ deviceDetails: (0, _expValEquals.expValEquals)('cc_editor_interactivity_monitoring', 'isEnabled', true) ? getDeviceDetails() : undefined,
177
193
  highPriority: true,
178
194
  tags: ['editor'],
179
195
  source: 'unknown'
180
196
  };
181
197
  (_this$insm$analyticsW = this.insm.analyticsWebClient) === null || _this$insm$analyticsW === void 0 || _this$insm$analyticsW.sendOperationalEvent(operationalEvent);
198
+ this.longAnimationFrameMeasurer.cleanup();
182
199
  }
183
200
  }]);
184
- }();
201
+ }(); // Functionality vendored from UFO
202
+ function getDeviceDetails() {
203
+ var _navigator$connection, _navigator$connection2, _navigator$connection3;
204
+ var telemetry = {};
205
+
206
+ // /platform/packages/data/ufo-internal/src/core/publisher/plugins/browser.ts
207
+ if (_bowserUltralight.default.getParser) {
208
+ var _ref;
209
+ var browser = _bowserUltralight.default.getParser(((_ref = typeof window !== 'undefined' && !!window ? window : undefined) === null || _ref === void 0 || (_ref = _ref.navigator) === null || _ref === void 0 ? void 0 : _ref.userAgent) || '');
210
+ Object.assign(telemetry, {
211
+ 'event:browser:name': browser.getBrowserName(),
212
+ 'event:browser:version': browser.getBrowserVersion()
213
+ });
214
+ }
215
+
216
+ // /platform/packages/data/ufo-internal/src/core/publisher/plugins/cpus.ts
217
+ Object.assign(telemetry, {
218
+ 'event:cpus': navigator.hardwareConcurrency
219
+ });
220
+
221
+ // /platform/packages/data/ufo-internal/src/core/publisher/plugins/memory.ts
222
+ // @ts-ignore: deviceMemory is exposed in some browsers
223
+ Object.assign(telemetry, {
224
+ 'event:memory': navigator.deviceMemory
225
+ });
226
+
227
+ // /platform/packages/data/ufo-internal/src/core/publisher/plugins/network.ts
228
+ Object.assign(telemetry, {
229
+ // Network
230
+ // @ts-ignore: connection is available in some browsers
231
+ // eslint-disable-next-line compat/compat
232
+ 'event:network:effectiveType': (_navigator$connection = navigator.connection) === null || _navigator$connection === void 0 ? void 0 : _navigator$connection.effectiveType,
233
+ // @ts-ignore: connection is available in some browsers
234
+ // eslint-disable-next-line compat/compat
235
+ 'event:network:rtt': (_navigator$connection2 = navigator.connection) === null || _navigator$connection2 === void 0 ? void 0 : _navigator$connection2.rtt,
236
+ // @ts-ignore: connection is available in some browsers
237
+ // eslint-disable-next-line compat/compat
238
+ 'event:network:downlink': (_navigator$connection3 = navigator.connection) === null || _navigator$connection3 === void 0 ? void 0 : _navigator$connection3.downlink
239
+ });
240
+ return telemetry;
241
+ }
package/dist/cjs/insm.js CHANGED
@@ -57,10 +57,11 @@ var INSM = exports.INSM = /*#__PURE__*/function () {
57
57
  return (0, _createClass2.default)(INSM, [{
58
58
  key: "startHeavyTask",
59
59
  value: function startHeavyTask(heavyTaskName) {
60
- var _this$runningSession2, _this$runningSession3;
60
+ var _this$runningSession2, _this$runningSession3, _this$session;
61
61
  this.runningHeavyTasks.add(heavyTaskName);
62
62
  (_this$runningSession2 = this.runningSession) === null || _this$runningSession2 === void 0 || (_this$runningSession2 = _this$runningSession2.periodTracking) === null || _this$runningSession2 === void 0 || _this$runningSession2.startHeavyTask(heavyTaskName);
63
63
  (_this$runningSession3 = this.runningSession) === null || _this$runningSession3 === void 0 || _this$runningSession3.periodTracking.pause(heavyTaskName);
64
+ (_this$session = this.session) === null || _this$session === void 0 || _this$session.longAnimationFrameMeasurer.pause();
64
65
  }
65
66
 
66
67
  /**
@@ -72,6 +73,10 @@ var INSM = exports.INSM = /*#__PURE__*/function () {
72
73
  var _this$runningSession4;
73
74
  this.runningHeavyTasks.delete(heavyTaskName);
74
75
  (_this$runningSession4 = this.runningSession) === null || _this$runningSession4 === void 0 || _this$runningSession4.periodTracking.resume(heavyTaskName);
76
+ if (this.runningHeavyTasks.size === 0) {
77
+ var _this$session2;
78
+ (_this$session2 = this.session) === null || _this$session2 === void 0 || _this$session2.longAnimationFrameMeasurer.resume();
79
+ }
75
80
  }
76
81
 
77
82
  /**
@@ -100,10 +105,31 @@ var INSM = exports.INSM = /*#__PURE__*/function () {
100
105
  contentId: experienceProperties.contentId
101
106
  });
102
107
  }
108
+ this.lastStartedExperienceProperties = experienceProperties;
103
109
  if ((_this$options$experie = this.options.experiences[experienceKey]) !== null && _this$options$experie !== void 0 && _this$options$experie.enabled) {
104
110
  this.runningSession = new _insmSession.INSMSession(experienceKey, experienceProperties, this);
105
111
  }
106
112
  }
113
+ }, {
114
+ key: "overrideExperienceKey",
115
+ value:
116
+ /**
117
+ * Call this to update the name of the running session after it's started
118
+ * In the case it's been started with an unregistered name, and there is not running
119
+ * session. This will also trigger the session being started.
120
+ */
121
+ function overrideExperienceKey(experienceKey) {
122
+ if (this.runningSession !== undefined) {
123
+ // If there is a running session - we update its name
124
+ this.runningSession.updateExperienceKey(experienceKey);
125
+ } else {
126
+ var _this$options$experie2;
127
+ // otherwise - we assume the last session was not registered, and start it with the new name
128
+ if ((_this$options$experie2 = this.options.experiences[experienceKey]) !== null && _this$options$experie2 !== void 0 && _this$options$experie2.enabled && this.lastStartedExperienceProperties) {
129
+ this.runningSession = new _insmSession.INSMSession(experienceKey, this.lastStartedExperienceProperties, this);
130
+ }
131
+ }
132
+ }
107
133
 
108
134
  /**
109
135
  * This prematurely halts any running experience measurement. It's expected to be used in
@@ -0,0 +1,122 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.LongAnimationFrameMeasurer = 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 LongAnimationFrameMeasurer = exports.LongAnimationFrameMeasurer = /*#__PURE__*/function () {
12
+ function LongAnimationFrameMeasurer(options) {
13
+ var _this = this;
14
+ (0, _classCallCheck2.default)(this, LongAnimationFrameMeasurer);
15
+ (0, _defineProperty2.default)(this, "longestAnimationFrames", []);
16
+ (0, _defineProperty2.default)(this, "minimumIndex", -1);
17
+ (0, _defineProperty2.default)(this, "minimumDuration", Infinity);
18
+ this.options = options;
19
+ this.paused = options.insmSession.insm.runningHeavyTasks.size !== 0;
20
+ if (PerformanceObserver.supportedEntryTypes.includes('long-animation-frame')) {
21
+ this.observer = new PerformanceObserver(function (list) {
22
+ if (!_this.paused) {
23
+ // only handle batches when tracking is not paused
24
+ _this.handleBatch(list.getEntries());
25
+ }
26
+ });
27
+ this.observer.observe({
28
+ type: 'long-animation-frame',
29
+ buffered: options.initial
30
+ });
31
+ }
32
+ }
33
+ return (0, _createClass2.default)(LongAnimationFrameMeasurer, [{
34
+ key: "handleBatch",
35
+ value: function handleBatch(batch) {
36
+ var len = this.longestAnimationFrames.length;
37
+ for (var batchIndex = 0; batchIndex < batch.length; batchIndex++) {
38
+ if (batch[batchIndex].duration < this.options.reportingThreshold) {
39
+ // If the long frame entry had a duration less than the reporting
40
+ // threshold it's not eligible for tracking
41
+ continue;
42
+ }
43
+ var longAnimationFrameEntry = Object.assign(batch[batchIndex], {
44
+ runningFeatures: Array.from(this.options.insmSession.runningFeatures)
45
+ });
46
+
47
+ // Not yet at capacity: append and update min tracking incrementally
48
+ if (len < this.options.limit) {
49
+ this.longestAnimationFrames[len] = longAnimationFrameEntry;
50
+ if (len === 0 || longAnimationFrameEntry.duration < this.minimumDuration) {
51
+ this.minimumDuration = longAnimationFrameEntry.duration;
52
+ this.minimumIndex = len;
53
+ }
54
+ len++;
55
+ continue;
56
+ }
57
+
58
+ // At capacity: only do work if we beat the current min
59
+ if (longAnimationFrameEntry.duration <= this.minimumDuration) {
60
+ continue;
61
+ }
62
+
63
+ // Replace the current min
64
+ this.longestAnimationFrames[this.minimumIndex] = longAnimationFrameEntry;
65
+ var _possiblyNewMinimumIndex = 0;
66
+ var _possibleNewMinimumDuration = this.longestAnimationFrames[0].duration;
67
+ for (var i = 1; i < this.options.limit; i++) {
68
+ if (this.longestAnimationFrames[i].duration < _possibleNewMinimumDuration) {
69
+ _possiblyNewMinimumIndex = i;
70
+ _possibleNewMinimumDuration = this.longestAnimationFrames[i].duration;
71
+ }
72
+ }
73
+
74
+ // Update the min tracking
75
+ this.minimumIndex = _possiblyNewMinimumIndex;
76
+ this.minimumDuration = _possibleNewMinimumDuration;
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Pauses tracking
82
+ */
83
+ }, {
84
+ key: "pause",
85
+ value: function pause() {
86
+ this.paused = true;
87
+ }
88
+
89
+ /**
90
+ * Resumes tracking
91
+ */
92
+ }, {
93
+ key: "resume",
94
+ value: function resume() {
95
+ this.paused = false;
96
+ }
97
+
98
+ /**
99
+ * Returns the current tracked longest animation frames sorted by duration
100
+ */
101
+ }, {
102
+ key: "current",
103
+ get: function get() {
104
+ var copy = this.longestAnimationFrames.slice();
105
+ copy.sort(function (a, b) {
106
+ return b.duration - a.duration;
107
+ });
108
+ return copy;
109
+ }
110
+
111
+ /**
112
+ * Cleans up the performance tracking (tracking cannot be resumed following this).
113
+ */
114
+ }, {
115
+ key: "cleanup",
116
+ value: function cleanup() {
117
+ var _this$observer;
118
+ (_this$observer = this.observer) === null || _this$observer === void 0 || _this$observer.disconnect();
119
+ }
120
+ }]);
121
+ }(); // Based on https://github.com/GoogleChrome/web-vitals/blob/1b872cf5f2159e8ace0e98d55d8eb54fb09adfbe/src/types.ts#L129
122
+ // Based on https://github.com/GoogleChrome/web-vitals/blob/1b872cf5f2159e8ace0e98d55d8eb54fb09adfbe/src/types.ts#L129
@@ -9,8 +9,6 @@ export function init(options) {
9
9
  }
10
10
  function insmInitialised() {
11
11
  if (!initialisedInsm) {
12
- // eslint-disable-next-line no-console
13
- console.error('INSM used when not initialised');
14
12
  return false;
15
13
  }
16
14
  return true;
@@ -35,6 +33,11 @@ export const insm = {
35
33
  initialisedInsm.start(experienceKey, experienceProperties);
36
34
  }
37
35
  },
36
+ overrideExperienceKey(experienceKey) {
37
+ if (insmInitialised()) {
38
+ initialisedInsm.overrideExperienceKey(experienceKey);
39
+ }
40
+ },
38
41
  stopEarly(reasonKey, description) {
39
42
  if (insmInitialised()) {
40
43
  initialisedInsm.stopEarly(reasonKey, description);
@@ -1,5 +1,8 @@
1
1
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+ import Bowser from 'bowser-ultralight';
3
+ import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
2
4
  import { PeriodTracking } from './insm-period';
5
+ import { LongAnimationFrameMeasurer } from './session-measurers/LongAnimationFrameMeasurer';
3
6
 
4
7
  /**
5
8
  * Only intended for internal use.
@@ -16,11 +19,20 @@ export class INSMSession {
16
19
  this.experienceProperties = experienceProperties;
17
20
  this.insm = insm;
18
21
  this.periodTracking = new PeriodTracking(this);
22
+ this.longAnimationFrameMeasurer = new LongAnimationFrameMeasurer({
23
+ initial: this.experienceProperties.initial,
24
+ limit: 3,
25
+ insmSession: this,
26
+ reportingThreshold: 500
27
+ });
19
28
 
20
29
  /**
21
30
  * Note: Events are not reliably fired from mobile browsers (ie. when a browser is closed when not in use)
22
31
  */
23
32
  }
33
+ updateExperienceKey(experienceKey) {
34
+ this.experienceKey = experienceKey;
35
+ }
24
36
 
25
37
  /**
26
38
  * Adds a feature to the currently running session
@@ -139,12 +151,57 @@ export class INSMSession {
139
151
  duration
140
152
  },
141
153
  periods: periodResults,
142
- endDetails: endDetails
154
+ endDetails: endDetails,
155
+ longAnimationFrames: this.longAnimationFrameMeasurer.current
143
156
  },
157
+ deviceDetails: expValEquals('cc_editor_interactivity_monitoring', 'isEnabled', true) ? getDeviceDetails() : undefined,
144
158
  highPriority: true,
145
159
  tags: ['editor'],
146
160
  source: 'unknown'
147
161
  };
148
162
  (_this$insm$analyticsW = this.insm.analyticsWebClient) === null || _this$insm$analyticsW === void 0 ? void 0 : _this$insm$analyticsW.sendOperationalEvent(operationalEvent);
163
+ this.longAnimationFrameMeasurer.cleanup();
164
+ }
165
+ }
166
+
167
+ // Functionality vendored from UFO
168
+ function getDeviceDetails() {
169
+ var _navigator$connection, _navigator$connection2, _navigator$connection3;
170
+ const telemetry = {};
171
+
172
+ // /platform/packages/data/ufo-internal/src/core/publisher/plugins/browser.ts
173
+ if (Bowser.getParser) {
174
+ var _ref, _ref$navigator;
175
+ const browser = Bowser.getParser(((_ref = typeof window !== 'undefined' && !!window ? window : undefined) === null || _ref === void 0 ? void 0 : (_ref$navigator = _ref.navigator) === null || _ref$navigator === void 0 ? void 0 : _ref$navigator.userAgent) || '');
176
+ Object.assign(telemetry, {
177
+ 'event:browser:name': browser.getBrowserName(),
178
+ 'event:browser:version': browser.getBrowserVersion()
179
+ });
149
180
  }
181
+
182
+ // /platform/packages/data/ufo-internal/src/core/publisher/plugins/cpus.ts
183
+ Object.assign(telemetry, {
184
+ 'event:cpus': navigator.hardwareConcurrency
185
+ });
186
+
187
+ // /platform/packages/data/ufo-internal/src/core/publisher/plugins/memory.ts
188
+ // @ts-ignore: deviceMemory is exposed in some browsers
189
+ Object.assign(telemetry, {
190
+ 'event:memory': navigator.deviceMemory
191
+ });
192
+
193
+ // /platform/packages/data/ufo-internal/src/core/publisher/plugins/network.ts
194
+ Object.assign(telemetry, {
195
+ // Network
196
+ // @ts-ignore: connection is available in some browsers
197
+ // eslint-disable-next-line compat/compat
198
+ 'event:network:effectiveType': (_navigator$connection = navigator.connection) === null || _navigator$connection === void 0 ? void 0 : _navigator$connection.effectiveType,
199
+ // @ts-ignore: connection is available in some browsers
200
+ // eslint-disable-next-line compat/compat
201
+ 'event:network:rtt': (_navigator$connection2 = navigator.connection) === null || _navigator$connection2 === void 0 ? void 0 : _navigator$connection2.rtt,
202
+ // @ts-ignore: connection is available in some browsers
203
+ // eslint-disable-next-line compat/compat
204
+ 'event:network:downlink': (_navigator$connection3 = navigator.connection) === null || _navigator$connection3 === void 0 ? void 0 : _navigator$connection3.downlink
205
+ });
206
+ return telemetry;
150
207
  }
@@ -42,10 +42,11 @@ export class INSM {
42
42
  * This also pauses measurement.
43
43
  */
44
44
  startHeavyTask(heavyTaskName) {
45
- var _this$runningSession2, _this$runningSession3, _this$runningSession4;
45
+ var _this$runningSession2, _this$runningSession3, _this$runningSession4, _this$session;
46
46
  this.runningHeavyTasks.add(heavyTaskName);
47
47
  (_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);
48
48
  (_this$runningSession4 = this.runningSession) === null || _this$runningSession4 === void 0 ? void 0 : _this$runningSession4.periodTracking.pause(heavyTaskName);
49
+ (_this$session = this.session) === null || _this$session === void 0 ? void 0 : _this$session.longAnimationFrameMeasurer.pause();
49
50
  }
50
51
 
51
52
  /**
@@ -55,6 +56,10 @@ export class INSM {
55
56
  var _this$runningSession5;
56
57
  this.runningHeavyTasks.delete(heavyTaskName);
57
58
  (_this$runningSession5 = this.runningSession) === null || _this$runningSession5 === void 0 ? void 0 : _this$runningSession5.periodTracking.resume(heavyTaskName);
59
+ if (this.runningHeavyTasks.size === 0) {
60
+ var _this$session2;
61
+ (_this$session2 = this.session) === null || _this$session2 === void 0 ? void 0 : _this$session2.longAnimationFrameMeasurer.resume();
62
+ }
58
63
  }
59
64
 
60
65
  /**
@@ -81,10 +86,28 @@ export class INSM {
81
86
  contentId: experienceProperties.contentId
82
87
  });
83
88
  }
89
+ this.lastStartedExperienceProperties = experienceProperties;
84
90
  if ((_this$options$experie = this.options.experiences[experienceKey]) !== null && _this$options$experie !== void 0 && _this$options$experie.enabled) {
85
91
  this.runningSession = new INSMSession(experienceKey, experienceProperties, this);
86
92
  }
87
93
  }
94
+ /**
95
+ * Call this to update the name of the running session after it's started
96
+ * In the case it's been started with an unregistered name, and there is not running
97
+ * session. This will also trigger the session being started.
98
+ */
99
+ overrideExperienceKey(experienceKey) {
100
+ if (this.runningSession !== undefined) {
101
+ // If there is a running session - we update its name
102
+ this.runningSession.updateExperienceKey(experienceKey);
103
+ } else {
104
+ var _this$options$experie2;
105
+ // otherwise - we assume the last session was not registered, and start it with the new name
106
+ if ((_this$options$experie2 = this.options.experiences[experienceKey]) !== null && _this$options$experie2 !== void 0 && _this$options$experie2.enabled && this.lastStartedExperienceProperties) {
107
+ this.runningSession = new INSMSession(experienceKey, this.lastStartedExperienceProperties, this);
108
+ }
109
+ }
110
+ }
88
111
 
89
112
  /**
90
113
  * This prematurely halts any running experience measurement. It's expected to be used in
@@ -0,0 +1,101 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+ export class LongAnimationFrameMeasurer {
3
+ constructor(options) {
4
+ _defineProperty(this, "longestAnimationFrames", []);
5
+ _defineProperty(this, "minimumIndex", -1);
6
+ _defineProperty(this, "minimumDuration", Infinity);
7
+ this.options = options;
8
+ this.paused = options.insmSession.insm.runningHeavyTasks.size !== 0;
9
+ if (PerformanceObserver.supportedEntryTypes.includes('long-animation-frame')) {
10
+ this.observer = new PerformanceObserver(list => {
11
+ if (!this.paused) {
12
+ // only handle batches when tracking is not paused
13
+ this.handleBatch(list.getEntries());
14
+ }
15
+ });
16
+ this.observer.observe({
17
+ type: 'long-animation-frame',
18
+ buffered: options.initial
19
+ });
20
+ }
21
+ }
22
+ handleBatch(batch) {
23
+ let len = this.longestAnimationFrames.length;
24
+ for (let batchIndex = 0; batchIndex < batch.length; batchIndex++) {
25
+ if (batch[batchIndex].duration < this.options.reportingThreshold) {
26
+ // If the long frame entry had a duration less than the reporting
27
+ // threshold it's not eligible for tracking
28
+ continue;
29
+ }
30
+ const longAnimationFrameEntry = Object.assign(batch[batchIndex], {
31
+ runningFeatures: Array.from(this.options.insmSession.runningFeatures)
32
+ });
33
+
34
+ // Not yet at capacity: append and update min tracking incrementally
35
+ if (len < this.options.limit) {
36
+ this.longestAnimationFrames[len] = longAnimationFrameEntry;
37
+ if (len === 0 || longAnimationFrameEntry.duration < this.minimumDuration) {
38
+ this.minimumDuration = longAnimationFrameEntry.duration;
39
+ this.minimumIndex = len;
40
+ }
41
+ len++;
42
+ continue;
43
+ }
44
+
45
+ // At capacity: only do work if we beat the current min
46
+ if (longAnimationFrameEntry.duration <= this.minimumDuration) {
47
+ continue;
48
+ }
49
+
50
+ // Replace the current min
51
+ this.longestAnimationFrames[this.minimumIndex] = longAnimationFrameEntry;
52
+ let _possiblyNewMinimumIndex = 0;
53
+ let _possibleNewMinimumDuration = this.longestAnimationFrames[0].duration;
54
+ for (let i = 1; i < this.options.limit; i++) {
55
+ if (this.longestAnimationFrames[i].duration < _possibleNewMinimumDuration) {
56
+ _possiblyNewMinimumIndex = i;
57
+ _possibleNewMinimumDuration = this.longestAnimationFrames[i].duration;
58
+ }
59
+ }
60
+
61
+ // Update the min tracking
62
+ this.minimumIndex = _possiblyNewMinimumIndex;
63
+ this.minimumDuration = _possibleNewMinimumDuration;
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Pauses tracking
69
+ */
70
+ pause() {
71
+ this.paused = true;
72
+ }
73
+
74
+ /**
75
+ * Resumes tracking
76
+ */
77
+ resume() {
78
+ this.paused = false;
79
+ }
80
+
81
+ /**
82
+ * Returns the current tracked longest animation frames sorted by duration
83
+ */
84
+ get current() {
85
+ const copy = this.longestAnimationFrames.slice();
86
+ copy.sort((a, b) => b.duration - a.duration);
87
+ return copy;
88
+ }
89
+
90
+ /**
91
+ * Cleans up the performance tracking (tracking cannot be resumed following this).
92
+ */
93
+ cleanup() {
94
+ var _this$observer;
95
+ (_this$observer = this.observer) === null || _this$observer === void 0 ? void 0 : _this$observer.disconnect();
96
+ }
97
+ }
98
+
99
+ // Based on https://github.com/GoogleChrome/web-vitals/blob/1b872cf5f2159e8ace0e98d55d8eb54fb09adfbe/src/types.ts#L129
100
+
101
+ // Based on https://github.com/GoogleChrome/web-vitals/blob/1b872cf5f2159e8ace0e98d55d8eb54fb09adfbe/src/types.ts#L129
package/dist/esm/index.js CHANGED
@@ -9,8 +9,6 @@ export function init(options) {
9
9
  }
10
10
  function insmInitialised() {
11
11
  if (!initialisedInsm) {
12
- // eslint-disable-next-line no-console
13
- console.error('INSM used when not initialised');
14
12
  return false;
15
13
  }
16
14
  return true;
@@ -35,6 +33,11 @@ export var insm = {
35
33
  initialisedInsm.start(experienceKey, experienceProperties);
36
34
  }
37
35
  },
36
+ overrideExperienceKey: function overrideExperienceKey(experienceKey) {
37
+ if (insmInitialised()) {
38
+ initialisedInsm.overrideExperienceKey(experienceKey);
39
+ }
40
+ },
38
41
  stopEarly: function stopEarly(reasonKey, description) {
39
42
  if (insmInitialised()) {
40
43
  initialisedInsm.stopEarly(reasonKey, description);
@@ -6,7 +6,10 @@ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t =
6
6
  function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
7
7
  function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
8
8
  function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
9
+ import Bowser from 'bowser-ultralight';
10
+ import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
9
11
  import { PeriodTracking } from './insm-period';
12
+ import { LongAnimationFrameMeasurer } from './session-measurers/LongAnimationFrameMeasurer';
10
13
 
11
14
  /**
12
15
  * Only intended for internal use.
@@ -24,16 +27,27 @@ export var INSMSession = /*#__PURE__*/function () {
24
27
  this.experienceProperties = experienceProperties;
25
28
  this.insm = insm;
26
29
  this.periodTracking = new PeriodTracking(this);
30
+ this.longAnimationFrameMeasurer = new LongAnimationFrameMeasurer({
31
+ initial: this.experienceProperties.initial,
32
+ limit: 3,
33
+ insmSession: this,
34
+ reportingThreshold: 500
35
+ });
27
36
 
28
37
  /**
29
38
  * Note: Events are not reliably fired from mobile browsers (ie. when a browser is closed when not in use)
30
39
  */
31
40
  }
32
-
33
- /**
34
- * Adds a feature to the currently running session
35
- */
36
41
  return _createClass(INSMSession, [{
42
+ key: "updateExperienceKey",
43
+ value: function updateExperienceKey(experienceKey) {
44
+ this.experienceKey = experienceKey;
45
+ }
46
+
47
+ /**
48
+ * Adds a feature to the currently running session
49
+ */
50
+ }, {
37
51
  key: "startFeature",
38
52
  value: function startFeature(featureName) {
39
53
  var _this$periodTracking;
@@ -166,13 +180,58 @@ export var INSMSession = /*#__PURE__*/function () {
166
180
  duration: duration
167
181
  },
168
182
  periods: periodResults,
169
- endDetails: endDetails
183
+ endDetails: endDetails,
184
+ longAnimationFrames: this.longAnimationFrameMeasurer.current
170
185
  }),
186
+ deviceDetails: expValEquals('cc_editor_interactivity_monitoring', 'isEnabled', true) ? getDeviceDetails() : undefined,
171
187
  highPriority: true,
172
188
  tags: ['editor'],
173
189
  source: 'unknown'
174
190
  };
175
191
  (_this$insm$analyticsW = this.insm.analyticsWebClient) === null || _this$insm$analyticsW === void 0 || _this$insm$analyticsW.sendOperationalEvent(operationalEvent);
192
+ this.longAnimationFrameMeasurer.cleanup();
176
193
  }
177
194
  }]);
178
- }();
195
+ }();
196
+
197
+ // Functionality vendored from UFO
198
+ function getDeviceDetails() {
199
+ var _navigator$connection, _navigator$connection2, _navigator$connection3;
200
+ var telemetry = {};
201
+
202
+ // /platform/packages/data/ufo-internal/src/core/publisher/plugins/browser.ts
203
+ if (Bowser.getParser) {
204
+ var _ref;
205
+ var browser = Bowser.getParser(((_ref = typeof window !== 'undefined' && !!window ? window : undefined) === null || _ref === void 0 || (_ref = _ref.navigator) === null || _ref === void 0 ? void 0 : _ref.userAgent) || '');
206
+ Object.assign(telemetry, {
207
+ 'event:browser:name': browser.getBrowserName(),
208
+ 'event:browser:version': browser.getBrowserVersion()
209
+ });
210
+ }
211
+
212
+ // /platform/packages/data/ufo-internal/src/core/publisher/plugins/cpus.ts
213
+ Object.assign(telemetry, {
214
+ 'event:cpus': navigator.hardwareConcurrency
215
+ });
216
+
217
+ // /platform/packages/data/ufo-internal/src/core/publisher/plugins/memory.ts
218
+ // @ts-ignore: deviceMemory is exposed in some browsers
219
+ Object.assign(telemetry, {
220
+ 'event:memory': navigator.deviceMemory
221
+ });
222
+
223
+ // /platform/packages/data/ufo-internal/src/core/publisher/plugins/network.ts
224
+ Object.assign(telemetry, {
225
+ // Network
226
+ // @ts-ignore: connection is available in some browsers
227
+ // eslint-disable-next-line compat/compat
228
+ 'event:network:effectiveType': (_navigator$connection = navigator.connection) === null || _navigator$connection === void 0 ? void 0 : _navigator$connection.effectiveType,
229
+ // @ts-ignore: connection is available in some browsers
230
+ // eslint-disable-next-line compat/compat
231
+ 'event:network:rtt': (_navigator$connection2 = navigator.connection) === null || _navigator$connection2 === void 0 ? void 0 : _navigator$connection2.rtt,
232
+ // @ts-ignore: connection is available in some browsers
233
+ // eslint-disable-next-line compat/compat
234
+ 'event:network:downlink': (_navigator$connection3 = navigator.connection) === null || _navigator$connection3 === void 0 ? void 0 : _navigator$connection3.downlink
235
+ });
236
+ return telemetry;
237
+ }
package/dist/esm/insm.js CHANGED
@@ -50,10 +50,11 @@ export var INSM = /*#__PURE__*/function () {
50
50
  return _createClass(INSM, [{
51
51
  key: "startHeavyTask",
52
52
  value: function startHeavyTask(heavyTaskName) {
53
- var _this$runningSession2, _this$runningSession3;
53
+ var _this$runningSession2, _this$runningSession3, _this$session;
54
54
  this.runningHeavyTasks.add(heavyTaskName);
55
55
  (_this$runningSession2 = this.runningSession) === null || _this$runningSession2 === void 0 || (_this$runningSession2 = _this$runningSession2.periodTracking) === null || _this$runningSession2 === void 0 || _this$runningSession2.startHeavyTask(heavyTaskName);
56
56
  (_this$runningSession3 = this.runningSession) === null || _this$runningSession3 === void 0 || _this$runningSession3.periodTracking.pause(heavyTaskName);
57
+ (_this$session = this.session) === null || _this$session === void 0 || _this$session.longAnimationFrameMeasurer.pause();
57
58
  }
58
59
 
59
60
  /**
@@ -65,6 +66,10 @@ export var INSM = /*#__PURE__*/function () {
65
66
  var _this$runningSession4;
66
67
  this.runningHeavyTasks.delete(heavyTaskName);
67
68
  (_this$runningSession4 = this.runningSession) === null || _this$runningSession4 === void 0 || _this$runningSession4.periodTracking.resume(heavyTaskName);
69
+ if (this.runningHeavyTasks.size === 0) {
70
+ var _this$session2;
71
+ (_this$session2 = this.session) === null || _this$session2 === void 0 || _this$session2.longAnimationFrameMeasurer.resume();
72
+ }
68
73
  }
69
74
 
70
75
  /**
@@ -93,10 +98,31 @@ export var INSM = /*#__PURE__*/function () {
93
98
  contentId: experienceProperties.contentId
94
99
  });
95
100
  }
101
+ this.lastStartedExperienceProperties = experienceProperties;
96
102
  if ((_this$options$experie = this.options.experiences[experienceKey]) !== null && _this$options$experie !== void 0 && _this$options$experie.enabled) {
97
103
  this.runningSession = new INSMSession(experienceKey, experienceProperties, this);
98
104
  }
99
105
  }
106
+ }, {
107
+ key: "overrideExperienceKey",
108
+ value:
109
+ /**
110
+ * Call this to update the name of the running session after it's started
111
+ * In the case it's been started with an unregistered name, and there is not running
112
+ * session. This will also trigger the session being started.
113
+ */
114
+ function overrideExperienceKey(experienceKey) {
115
+ if (this.runningSession !== undefined) {
116
+ // If there is a running session - we update its name
117
+ this.runningSession.updateExperienceKey(experienceKey);
118
+ } else {
119
+ var _this$options$experie2;
120
+ // otherwise - we assume the last session was not registered, and start it with the new name
121
+ if ((_this$options$experie2 = this.options.experiences[experienceKey]) !== null && _this$options$experie2 !== void 0 && _this$options$experie2.enabled && this.lastStartedExperienceProperties) {
122
+ this.runningSession = new INSMSession(experienceKey, this.lastStartedExperienceProperties, this);
123
+ }
124
+ }
125
+ }
100
126
 
101
127
  /**
102
128
  * This prematurely halts any running experience measurement. It's expected to be used in
@@ -0,0 +1,118 @@
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 LongAnimationFrameMeasurer = /*#__PURE__*/function () {
5
+ function LongAnimationFrameMeasurer(options) {
6
+ var _this = this;
7
+ _classCallCheck(this, LongAnimationFrameMeasurer);
8
+ _defineProperty(this, "longestAnimationFrames", []);
9
+ _defineProperty(this, "minimumIndex", -1);
10
+ _defineProperty(this, "minimumDuration", Infinity);
11
+ this.options = options;
12
+ this.paused = options.insmSession.insm.runningHeavyTasks.size !== 0;
13
+ if (PerformanceObserver.supportedEntryTypes.includes('long-animation-frame')) {
14
+ this.observer = new PerformanceObserver(function (list) {
15
+ if (!_this.paused) {
16
+ // only handle batches when tracking is not paused
17
+ _this.handleBatch(list.getEntries());
18
+ }
19
+ });
20
+ this.observer.observe({
21
+ type: 'long-animation-frame',
22
+ buffered: options.initial
23
+ });
24
+ }
25
+ }
26
+ return _createClass(LongAnimationFrameMeasurer, [{
27
+ key: "handleBatch",
28
+ value: function handleBatch(batch) {
29
+ var len = this.longestAnimationFrames.length;
30
+ for (var batchIndex = 0; batchIndex < batch.length; batchIndex++) {
31
+ if (batch[batchIndex].duration < this.options.reportingThreshold) {
32
+ // If the long frame entry had a duration less than the reporting
33
+ // threshold it's not eligible for tracking
34
+ continue;
35
+ }
36
+ var longAnimationFrameEntry = Object.assign(batch[batchIndex], {
37
+ runningFeatures: Array.from(this.options.insmSession.runningFeatures)
38
+ });
39
+
40
+ // Not yet at capacity: append and update min tracking incrementally
41
+ if (len < this.options.limit) {
42
+ this.longestAnimationFrames[len] = longAnimationFrameEntry;
43
+ if (len === 0 || longAnimationFrameEntry.duration < this.minimumDuration) {
44
+ this.minimumDuration = longAnimationFrameEntry.duration;
45
+ this.minimumIndex = len;
46
+ }
47
+ len++;
48
+ continue;
49
+ }
50
+
51
+ // At capacity: only do work if we beat the current min
52
+ if (longAnimationFrameEntry.duration <= this.minimumDuration) {
53
+ continue;
54
+ }
55
+
56
+ // Replace the current min
57
+ this.longestAnimationFrames[this.minimumIndex] = longAnimationFrameEntry;
58
+ var _possiblyNewMinimumIndex = 0;
59
+ var _possibleNewMinimumDuration = this.longestAnimationFrames[0].duration;
60
+ for (var i = 1; i < this.options.limit; i++) {
61
+ if (this.longestAnimationFrames[i].duration < _possibleNewMinimumDuration) {
62
+ _possiblyNewMinimumIndex = i;
63
+ _possibleNewMinimumDuration = this.longestAnimationFrames[i].duration;
64
+ }
65
+ }
66
+
67
+ // Update the min tracking
68
+ this.minimumIndex = _possiblyNewMinimumIndex;
69
+ this.minimumDuration = _possibleNewMinimumDuration;
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Pauses tracking
75
+ */
76
+ }, {
77
+ key: "pause",
78
+ value: function pause() {
79
+ this.paused = true;
80
+ }
81
+
82
+ /**
83
+ * Resumes tracking
84
+ */
85
+ }, {
86
+ key: "resume",
87
+ value: function resume() {
88
+ this.paused = false;
89
+ }
90
+
91
+ /**
92
+ * Returns the current tracked longest animation frames sorted by duration
93
+ */
94
+ }, {
95
+ key: "current",
96
+ get: function get() {
97
+ var copy = this.longestAnimationFrames.slice();
98
+ copy.sort(function (a, b) {
99
+ return b.duration - a.duration;
100
+ });
101
+ return copy;
102
+ }
103
+
104
+ /**
105
+ * Cleans up the performance tracking (tracking cannot be resumed following this).
106
+ */
107
+ }, {
108
+ key: "cleanup",
109
+ value: function cleanup() {
110
+ var _this$observer;
111
+ (_this$observer = this.observer) === null || _this$observer === void 0 || _this$observer.disconnect();
112
+ }
113
+ }]);
114
+ }();
115
+
116
+ // Based on https://github.com/GoogleChrome/web-vitals/blob/1b872cf5f2159e8ace0e98d55d8eb54fb09adfbe/src/types.ts#L129
117
+
118
+ // Based on https://github.com/GoogleChrome/web-vitals/blob/1b872cf5f2159e8ace0e98d55d8eb54fb09adfbe/src/types.ts#L129
@@ -8,6 +8,6 @@ export declare function init(options: INSMOptions): void;
8
8
  /**
9
9
  * **In**teractivity **s**ession **m**onitoring
10
10
  */
11
- export declare const insm: Pick<INSM, 'start' | 'stopEarly' | 'startHeavyTask' | 'endHeavyTask'> & {
11
+ export declare const insm: Pick<INSM, 'start' | 'stopEarly' | 'startHeavyTask' | 'endHeavyTask' | 'overrideExperienceKey'> & {
12
12
  session: Pick<INSMSession, 'details' | 'startFeature' | 'endFeature' | 'addProperties'> | undefined;
13
13
  };
@@ -1,6 +1,7 @@
1
1
  import type { INSM } from './insm';
2
2
  import type { AddedProperties, ExperienceProperties } from './types';
3
3
  import { PeriodTracking } from './insm-period';
4
+ import { LongAnimationFrameMeasurer } from './session-measurers/LongAnimationFrameMeasurer';
4
5
  /**
5
6
  * Only intended for internal use.
6
7
  *
@@ -15,7 +16,9 @@ export declare class INSMSession {
15
16
  private addedProperties;
16
17
  runningFeatures: Set<string>;
17
18
  periodTracking: PeriodTracking;
19
+ longAnimationFrameMeasurer: LongAnimationFrameMeasurer;
18
20
  constructor(experienceKey: string, experienceProperties: ExperienceProperties, insm: INSM);
21
+ updateExperienceKey(experienceKey: string): void;
19
22
  /**
20
23
  * Adds a feature to the currently running session
21
24
  */
@@ -84,8 +87,8 @@ export declare class INSMSession {
84
87
  } | {
85
88
  stoppedBy: 'beforeunload';
86
89
  } | {
87
- stoppedBy: 'early-stop';
88
- reason: string;
89
90
  description?: string;
91
+ reason: string;
92
+ stoppedBy: 'early-stop';
90
93
  }): void;
91
94
  }
@@ -41,6 +41,13 @@ export declare class INSM {
41
41
  * ```
42
42
  */
43
43
  start(experienceKey: string, experienceProperties: ExperienceProperties): void;
44
+ private lastStartedExperienceProperties;
45
+ /**
46
+ * Call this to update the name of the running session after it's started
47
+ * In the case it's been started with an unregistered name, and there is not running
48
+ * session. This will also trigger the session being started.
49
+ */
50
+ overrideExperienceKey(experienceKey: string): void;
44
51
  /**
45
52
  * This prematurely halts any running experience measurement. It's expected to be used in
46
53
  * scenarios such as when error boundaries are hit.
@@ -0,0 +1,61 @@
1
+ import type { INSMSession } from '../insm-session';
2
+ type LongAnimationFrameMeasurerOptions = {
3
+ initial: boolean;
4
+ limit: number;
5
+ insmSession: INSMSession;
6
+ reportingThreshold: number;
7
+ };
8
+ export declare class LongAnimationFrameMeasurer {
9
+ private observer?;
10
+ private longestAnimationFrames;
11
+ private options;
12
+ private paused;
13
+ private minimumIndex;
14
+ private minimumDuration;
15
+ constructor(options: LongAnimationFrameMeasurerOptions);
16
+ private handleBatch;
17
+ /**
18
+ * Pauses tracking
19
+ */
20
+ pause(): void;
21
+ /**
22
+ * Resumes tracking
23
+ */
24
+ resume(): void;
25
+ /**
26
+ * Returns the current tracked longest animation frames sorted by duration
27
+ */
28
+ get current(): PerformanceLongAnimationFrameTiming[];
29
+ /**
30
+ * Cleans up the performance tracking (tracking cannot be resumed following this).
31
+ */
32
+ cleanup(): void;
33
+ }
34
+ interface PerformanceScriptTiming extends PerformanceEntry {
35
+ readonly startTime: DOMHighResTimeStamp;
36
+ readonly duration: DOMHighResTimeStamp;
37
+ readonly name: string;
38
+ readonly entryType: string;
39
+ readonly invokerType: 'classic-script' | 'module-script' | 'event-listener' | 'user-callback' | 'resolve-promise' | 'reject-promise';
40
+ readonly invoker: string;
41
+ readonly executionStart: DOMHighResTimeStamp;
42
+ readonly sourceURL: string;
43
+ readonly sourceFunctionName: string;
44
+ readonly sourceCharPosition: number;
45
+ readonly pauseDuration: DOMHighResTimeStamp;
46
+ readonly forcedStyleAndLayoutDuration: DOMHighResTimeStamp;
47
+ readonly window?: Window;
48
+ readonly windowAttribution: 'self' | 'descendant' | 'ancestor' | 'same-page' | 'other';
49
+ }
50
+ interface PerformanceLongAnimationFrameTiming extends PerformanceEntry {
51
+ readonly startTime: DOMHighResTimeStamp;
52
+ readonly duration: DOMHighResTimeStamp;
53
+ readonly name: string;
54
+ readonly entryType: string;
55
+ readonly renderStart: DOMHighResTimeStamp;
56
+ readonly styleAndLayoutStart: DOMHighResTimeStamp;
57
+ readonly blockingDuration: DOMHighResTimeStamp;
58
+ readonly firstUIEventTimestamp: DOMHighResTimeStamp;
59
+ readonly scripts: PerformanceScriptTiming[];
60
+ }
61
+ export {};
@@ -8,6 +8,6 @@ export declare function init(options: INSMOptions): void;
8
8
  /**
9
9
  * **In**teractivity **s**ession **m**onitoring
10
10
  */
11
- export declare const insm: Pick<INSM, 'start' | 'stopEarly' | 'startHeavyTask' | 'endHeavyTask'> & {
11
+ export declare const insm: Pick<INSM, 'start' | 'stopEarly' | 'startHeavyTask' | 'endHeavyTask' | 'overrideExperienceKey'> & {
12
12
  session: Pick<INSMSession, 'details' | 'startFeature' | 'endFeature' | 'addProperties'> | undefined;
13
13
  };
@@ -1,6 +1,7 @@
1
1
  import type { INSM } from './insm';
2
2
  import type { AddedProperties, ExperienceProperties } from './types';
3
3
  import { PeriodTracking } from './insm-period';
4
+ import { LongAnimationFrameMeasurer } from './session-measurers/LongAnimationFrameMeasurer';
4
5
  /**
5
6
  * Only intended for internal use.
6
7
  *
@@ -15,7 +16,9 @@ export declare class INSMSession {
15
16
  private addedProperties;
16
17
  runningFeatures: Set<string>;
17
18
  periodTracking: PeriodTracking;
19
+ longAnimationFrameMeasurer: LongAnimationFrameMeasurer;
18
20
  constructor(experienceKey: string, experienceProperties: ExperienceProperties, insm: INSM);
21
+ updateExperienceKey(experienceKey: string): void;
19
22
  /**
20
23
  * Adds a feature to the currently running session
21
24
  */
@@ -84,8 +87,8 @@ export declare class INSMSession {
84
87
  } | {
85
88
  stoppedBy: 'beforeunload';
86
89
  } | {
87
- stoppedBy: 'early-stop';
88
- reason: string;
89
90
  description?: string;
91
+ reason: string;
92
+ stoppedBy: 'early-stop';
90
93
  }): void;
91
94
  }
@@ -44,6 +44,13 @@ export declare class INSM {
44
44
  * ```
45
45
  */
46
46
  start(experienceKey: string, experienceProperties: ExperienceProperties): void;
47
+ private lastStartedExperienceProperties;
48
+ /**
49
+ * Call this to update the name of the running session after it's started
50
+ * In the case it's been started with an unregistered name, and there is not running
51
+ * session. This will also trigger the session being started.
52
+ */
53
+ overrideExperienceKey(experienceKey: string): void;
47
54
  /**
48
55
  * This prematurely halts any running experience measurement. It's expected to be used in
49
56
  * scenarios such as when error boundaries are hit.
@@ -0,0 +1,61 @@
1
+ import type { INSMSession } from '../insm-session';
2
+ type LongAnimationFrameMeasurerOptions = {
3
+ initial: boolean;
4
+ limit: number;
5
+ insmSession: INSMSession;
6
+ reportingThreshold: number;
7
+ };
8
+ export declare class LongAnimationFrameMeasurer {
9
+ private observer?;
10
+ private longestAnimationFrames;
11
+ private options;
12
+ private paused;
13
+ private minimumIndex;
14
+ private minimumDuration;
15
+ constructor(options: LongAnimationFrameMeasurerOptions);
16
+ private handleBatch;
17
+ /**
18
+ * Pauses tracking
19
+ */
20
+ pause(): void;
21
+ /**
22
+ * Resumes tracking
23
+ */
24
+ resume(): void;
25
+ /**
26
+ * Returns the current tracked longest animation frames sorted by duration
27
+ */
28
+ get current(): PerformanceLongAnimationFrameTiming[];
29
+ /**
30
+ * Cleans up the performance tracking (tracking cannot be resumed following this).
31
+ */
32
+ cleanup(): void;
33
+ }
34
+ interface PerformanceScriptTiming extends PerformanceEntry {
35
+ readonly startTime: DOMHighResTimeStamp;
36
+ readonly duration: DOMHighResTimeStamp;
37
+ readonly name: string;
38
+ readonly entryType: string;
39
+ readonly invokerType: 'classic-script' | 'module-script' | 'event-listener' | 'user-callback' | 'resolve-promise' | 'reject-promise';
40
+ readonly invoker: string;
41
+ readonly executionStart: DOMHighResTimeStamp;
42
+ readonly sourceURL: string;
43
+ readonly sourceFunctionName: string;
44
+ readonly sourceCharPosition: number;
45
+ readonly pauseDuration: DOMHighResTimeStamp;
46
+ readonly forcedStyleAndLayoutDuration: DOMHighResTimeStamp;
47
+ readonly window?: Window;
48
+ readonly windowAttribution: 'self' | 'descendant' | 'ancestor' | 'same-page' | 'other';
49
+ }
50
+ interface PerformanceLongAnimationFrameTiming extends PerformanceEntry {
51
+ readonly startTime: DOMHighResTimeStamp;
52
+ readonly duration: DOMHighResTimeStamp;
53
+ readonly name: string;
54
+ readonly entryType: string;
55
+ readonly renderStart: DOMHighResTimeStamp;
56
+ readonly styleAndLayoutStart: DOMHighResTimeStamp;
57
+ readonly blockingDuration: DOMHighResTimeStamp;
58
+ readonly firstUIEventTimestamp: DOMHighResTimeStamp;
59
+ readonly scripts: PerformanceScriptTiming[];
60
+ }
61
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/insm",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "INSM tooling measures user-perceived interactivity of a page",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -25,9 +25,10 @@
25
25
  },
26
26
  "atlaskit:src": "src/index.ts",
27
27
  "dependencies": {
28
- "@atlaskit/analytics-listeners": "^9.0.0",
29
- "@atlaskit/tmp-editor-statsig": "^12.5.0",
30
- "@babel/runtime": "^7.0.0"
28
+ "@atlaskit/analytics-listeners": "^9.1.0",
29
+ "@atlaskit/tmp-editor-statsig": "^13.0.0",
30
+ "@babel/runtime": "^7.0.0",
31
+ "bowser-ultralight": "^1.0.6"
31
32
  },
32
33
  "peerDependencies": {
33
34
  "react": "^18.2.0"