@atlaskit/insm 0.1.1 → 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,20 @@
1
1
  # @atlaskit/insm
2
2
 
3
+ ## 0.1.3
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies
8
+
9
+ ## 0.1.2
10
+
11
+ ### Patch Changes
12
+
13
+ - [`cf1cfd5a04c5c`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/cf1cfd5a04c5c) -
14
+ Remove INSM circular dependency
15
+ - [`78573b5014067`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/78573b5014067) -
16
+ Update numerator calculation in INP Measurers
17
+
3
18
  ## 0.1.1
4
19
 
5
20
  ### 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);
@@ -60,7 +60,7 @@ var InteractionResult = /*#__PURE__*/function () {
60
60
  if (duration < this.min) {
61
61
  this.min = duration;
62
62
  }
63
- this.numerator += this.average * (this.count - 1) + duration;
63
+ this.numerator += duration;
64
64
  this.count += 1;
65
65
  this.average = this.numerator / this.count;
66
66
  }
@@ -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,12 +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
  }),
177
- tags: ['insm'],
178
- source: 'insm'
192
+ deviceDetails: (0, _expValEquals.expValEquals)('cc_editor_interactivity_monitoring', 'isEnabled', true) ? getDeviceDetails() : undefined,
193
+ highPriority: true,
194
+ tags: ['editor'],
195
+ source: 'unknown'
179
196
  };
180
197
  (_this$insm$analyticsW = this.insm.analyticsWebClient) === null || _this$insm$analyticsW === void 0 || _this$insm$analyticsW.sendOperationalEvent(operationalEvent);
198
+ this.longAnimationFrameMeasurer.cleanup();
181
199
  }
182
200
  }]);
183
- }();
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
@@ -21,17 +21,7 @@ var INSM = exports.INSM = /*#__PURE__*/function () {
21
21
  * page session.
22
22
  */
23
23
  (0, _defineProperty2.default)(this, "runningHeavyTasks", new Set());
24
- this.periodMeasurers = [new _afps.AnimationFPSIM(), new _inp.INPTracker(), new _inp.INPTracker({
25
- includedInteractions: ['pointerup']
26
- }), new _inp.INPTracker({
27
- includedInteractions: ['pointerdown']
28
- }), new _inp.INPTracker({
29
- includedInteractions: ['click']
30
- }), new _inp.INPTracker({
31
- includedInteractions: ['keydown']
32
- }), new _inp.INPTracker({
33
- includedInteractions: ['keyup']
34
- })];
24
+ this.periodMeasurers = [new _afps.AnimationFPSIM(), new _inp.INPTracker()];
35
25
  this.options = options;
36
26
 
37
27
  // If this does throw -- we do want an unhandledRejection rejection to be passed to the window
@@ -40,14 +30,23 @@ var INSM = exports.INSM = /*#__PURE__*/function () {
40
30
  return _this.analyticsWebClient = analyticsWebClient;
41
31
  });
42
32
 
43
- // No cleanup needs to be performed -- as this is intended to run until the tab is closed
44
- // eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
45
- window.addEventListener('pagehide', function () {
46
- var _this$runningSession;
47
- (_this$runningSession = _this.runningSession) === null || _this$runningSession === void 0 || _this$runningSession.end({
48
- stoppedBy: 'pagehide'
33
+ // No cleanup needs to be performed -- as this tooling is intended to run until the tab is closed
34
+ // The use of beforeunload here is because using pagehide does not reliably result in the analytics
35
+ // being fired by the analytics web client.
36
+ // The use of this means we will miss events from mobile safari which does not reliably call this api
37
+ // however mobile browsers do not call when a browser closes - so we expect limited mobile data from
38
+ // this tooling in its current form.
39
+
40
+ // window will not be defined in server envs - in these envs -- there can never be a session end
41
+ if (typeof window !== 'undefined') {
42
+ // eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
43
+ window.addEventListener('beforeunload', function () {
44
+ var _this$runningSession;
45
+ (_this$runningSession = _this.runningSession) === null || _this$runningSession === void 0 || _this$runningSession.end({
46
+ stoppedBy: 'beforeunload'
47
+ });
49
48
  });
50
- });
49
+ }
51
50
  }
52
51
 
53
52
  /**
@@ -58,10 +57,11 @@ var INSM = exports.INSM = /*#__PURE__*/function () {
58
57
  return (0, _createClass2.default)(INSM, [{
59
58
  key: "startHeavyTask",
60
59
  value: function startHeavyTask(heavyTaskName) {
61
- var _this$runningSession2, _this$runningSession3;
60
+ var _this$runningSession2, _this$runningSession3, _this$session;
62
61
  this.runningHeavyTasks.add(heavyTaskName);
63
62
  (_this$runningSession2 = this.runningSession) === null || _this$runningSession2 === void 0 || (_this$runningSession2 = _this$runningSession2.periodTracking) === null || _this$runningSession2 === void 0 || _this$runningSession2.startHeavyTask(heavyTaskName);
64
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();
65
65
  }
66
66
 
67
67
  /**
@@ -73,6 +73,10 @@ var INSM = exports.INSM = /*#__PURE__*/function () {
73
73
  var _this$runningSession4;
74
74
  this.runningHeavyTasks.delete(heavyTaskName);
75
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
+ }
76
80
  }
77
81
 
78
82
  /**
@@ -101,10 +105,31 @@ var INSM = exports.INSM = /*#__PURE__*/function () {
101
105
  contentId: experienceProperties.contentId
102
106
  });
103
107
  }
108
+ this.lastStartedExperienceProperties = experienceProperties;
104
109
  if ((_this$options$experie = this.options.experiences[experienceKey]) !== null && _this$options$experie !== void 0 && _this$options$experie.enabled) {
105
110
  this.runningSession = new _insmSession.INSMSession(experienceKey, experienceProperties, this);
106
111
  }
107
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
+ }
108
133
 
109
134
  /**
110
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);
@@ -38,7 +38,7 @@ class InteractionResult {
38
38
  if (duration < this.min) {
39
39
  this.min = duration;
40
40
  }
41
- this.numerator += this.average * (this.count - 1) + duration;
41
+ this.numerator += duration;
42
42
  this.count += 1;
43
43
  this.average = this.numerator / this.count;
44
44
  }
@@ -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,11 +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
  },
144
- tags: ['insm'],
145
- source: 'insm'
157
+ deviceDetails: expValEquals('cc_editor_interactivity_monitoring', 'isEnabled', true) ? getDeviceDetails() : undefined,
158
+ highPriority: true,
159
+ tags: ['editor'],
160
+ source: 'unknown'
146
161
  };
147
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
+ });
148
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;
149
207
  }
@@ -10,31 +10,30 @@ export class INSM {
10
10
  * page session.
11
11
  */
12
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
- })];
13
+ this.periodMeasurers = [new AnimationFPSIM(), new INPTracker()];
24
14
  this.options = options;
25
15
 
26
16
  // If this does throw -- we do want an unhandledRejection rejection to be passed to the window
27
17
  // this is to ease debugging.
28
18
  options.getAnalyticsWebClient.then(analyticsWebClient => this.analyticsWebClient = analyticsWebClient);
29
19
 
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'
20
+ // No cleanup needs to be performed -- as this tooling is intended to run until the tab is closed
21
+ // The use of beforeunload here is because using pagehide does not reliably result in the analytics
22
+ // being fired by the analytics web client.
23
+ // The use of this means we will miss events from mobile safari which does not reliably call this api
24
+ // however mobile browsers do not call when a browser closes - so we expect limited mobile data from
25
+ // this tooling in its current form.
26
+
27
+ // window will not be defined in server envs - in these envs -- there can never be a session end
28
+ if (typeof window !== 'undefined') {
29
+ // eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
30
+ window.addEventListener('beforeunload', () => {
31
+ var _this$runningSession;
32
+ (_this$runningSession = this.runningSession) === null || _this$runningSession === void 0 ? void 0 : _this$runningSession.end({
33
+ stoppedBy: 'beforeunload'
34
+ });
36
35
  });
37
- });
36
+ }
38
37
  }
39
38
 
40
39
  /**
@@ -43,10 +42,11 @@ export class INSM {
43
42
  * This also pauses measurement.
44
43
  */
45
44
  startHeavyTask(heavyTaskName) {
46
- var _this$runningSession2, _this$runningSession3, _this$runningSession4;
45
+ var _this$runningSession2, _this$runningSession3, _this$runningSession4, _this$session;
47
46
  this.runningHeavyTasks.add(heavyTaskName);
48
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);
49
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();
50
50
  }
51
51
 
52
52
  /**
@@ -56,6 +56,10 @@ export class INSM {
56
56
  var _this$runningSession5;
57
57
  this.runningHeavyTasks.delete(heavyTaskName);
58
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
+ }
59
63
  }
60
64
 
61
65
  /**
@@ -82,10 +86,28 @@ export class INSM {
82
86
  contentId: experienceProperties.contentId
83
87
  });
84
88
  }
89
+ this.lastStartedExperienceProperties = experienceProperties;
85
90
  if ((_this$options$experie = this.options.experiences[experienceKey]) !== null && _this$options$experie !== void 0 && _this$options$experie.enabled) {
86
91
  this.runningSession = new INSMSession(experienceKey, experienceProperties, this);
87
92
  }
88
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
+ }
89
111
 
90
112
  /**
91
113
  * This prematurely halts any running experience measurement. It's expected to be used in