@atlaskit/editor-plugin-interactivity 0.1.0 → 0.2.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 (51) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +34 -6
  3. package/afm-cc/tsconfig.json +12 -0
  4. package/afm-products/tsconfig.json +12 -0
  5. package/dist/cjs/analytics/fire-interactivity-event.js +39 -0
  6. package/dist/cjs/analytics/interactivity-snapshot.js +1 -0
  7. package/dist/cjs/collector/bucket-boundaries.js +137 -0
  8. package/dist/cjs/collector/interaction-group.js +167 -0
  9. package/dist/cjs/collector/interaction-observer.js +114 -0
  10. package/dist/cjs/collector/interaction-tracker.js +144 -0
  11. package/dist/cjs/collector/interactivity-collector.js +289 -0
  12. package/dist/cjs/collector/interactivity-session.js +52 -0
  13. package/dist/cjs/collector/lifecycle-observer.js +66 -0
  14. package/dist/cjs/collector/snapshot-scheduler.js +70 -0
  15. package/dist/cjs/interactivityPlugin.js +81 -6
  16. package/dist/es2019/analytics/fire-interactivity-event.js +33 -0
  17. package/dist/es2019/analytics/interactivity-snapshot.js +0 -0
  18. package/dist/es2019/collector/bucket-boundaries.js +117 -0
  19. package/dist/es2019/collector/interaction-group.js +110 -0
  20. package/dist/es2019/collector/interaction-observer.js +90 -0
  21. package/dist/es2019/collector/interaction-tracker.js +116 -0
  22. package/dist/es2019/collector/interactivity-collector.js +228 -0
  23. package/dist/es2019/collector/interactivity-session.js +42 -0
  24. package/dist/es2019/collector/lifecycle-observer.js +46 -0
  25. package/dist/es2019/collector/snapshot-scheduler.js +48 -0
  26. package/dist/es2019/interactivityPlugin.js +75 -6
  27. package/dist/esm/analytics/fire-interactivity-event.js +33 -0
  28. package/dist/esm/analytics/interactivity-snapshot.js +0 -0
  29. package/dist/esm/collector/bucket-boundaries.js +130 -0
  30. package/dist/esm/collector/interaction-group.js +161 -0
  31. package/dist/esm/collector/interaction-observer.js +108 -0
  32. package/dist/esm/collector/interaction-tracker.js +137 -0
  33. package/dist/esm/collector/interactivity-collector.js +282 -0
  34. package/dist/esm/collector/interactivity-session.js +45 -0
  35. package/dist/esm/collector/lifecycle-observer.js +59 -0
  36. package/dist/esm/collector/snapshot-scheduler.js +63 -0
  37. package/dist/esm/interactivityPlugin.js +81 -6
  38. package/dist/types/analytics/fire-interactivity-event.d.ts +9 -0
  39. package/dist/types/analytics/interactivity-snapshot.d.ts +61 -0
  40. package/dist/types/collector/bucket-boundaries.d.ts +35 -0
  41. package/dist/types/collector/interaction-group.d.ts +25 -0
  42. package/dist/types/collector/interaction-observer.d.ts +39 -0
  43. package/dist/types/collector/interaction-tracker.d.ts +56 -0
  44. package/dist/types/collector/interactivity-collector.d.ts +75 -0
  45. package/dist/types/collector/interactivity-session.d.ts +39 -0
  46. package/dist/types/collector/lifecycle-observer.d.ts +25 -0
  47. package/dist/types/collector/snapshot-scheduler.d.ts +15 -0
  48. package/dist/types/interactivityPlugin.d.ts +6 -4
  49. package/dist/types/interactivityPluginType.d.ts +11 -2
  50. package/docs/0-intro.tsx +25 -7
  51. package/package.json +8 -3
@@ -0,0 +1,161 @@
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
+ 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; } } }; }
5
+ 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; } }
6
+ 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; }
7
+ import { bucketKeyForMs } from './bucket-boundaries';
8
+
9
+ /**
10
+ * Latencies are counted per 8 ms, the resolution Event Timing reports durations at. Every
11
+ * latency is rounded up to this step on the way in, which caps the number of distinct values
12
+ * a group can hold whatever the latency was derived from.
13
+ */
14
+ var RESOLUTION_MS = 8;
15
+
16
+ /** Which percentiles are reported, as quantiles. */
17
+ var REPORTED_QUANTILES = [0.9, 0.98];
18
+
19
+ /**
20
+ * The latencies of one set of interactions — every interaction on the page, or only the ones
21
+ * inside the editor — reported as one object in the event.
22
+ *
23
+ * The whole state is a count per distinct latency, so an interaction only costs a counter
24
+ * whatever its latency was, and everything the event carries — the reported buckets, the
25
+ * count, the sum, the maximum and the percentiles — is derived from that map when a snapshot
26
+ * is taken. Nothing is computed while interactions arrive.
27
+ */
28
+ export var InteractionGroup = /*#__PURE__*/function () {
29
+ function InteractionGroup() {
30
+ _classCallCheck(this, InteractionGroup);
31
+ _defineProperty(this, "countByLatency", new Map());
32
+ }
33
+ return _createClass(InteractionGroup, [{
34
+ key: "add",
35
+ value: function add(latencyMs) {
36
+ this.increment(latencyMs);
37
+ }
38
+ }, {
39
+ key: "remeasure",
40
+ value: function remeasure(previousLatencyMs, latencyMs) {
41
+ // Moved rather than counted again: the count belongs to the same interaction.
42
+ this.decrement(previousLatencyMs);
43
+ this.increment(latencyMs);
44
+ }
45
+
46
+ /**
47
+ * @param totalCount every interaction of the group, including those below the Event Timing
48
+ * reporting threshold, which this group never sees.
49
+ */
50
+ }, {
51
+ key: "snapshot",
52
+ value: function snapshot(totalCount) {
53
+ var _latencies;
54
+ // Ascending, so the reported buckets come out in order and the last latency is the
55
+ // maximum. Sorted once for everything below.
56
+ var latencies = Array.from(this.countByLatency.keys()).sort(function (a, b) {
57
+ return a - b;
58
+ });
59
+ var observedCount = this.observedCount();
60
+ var percentileRanks = REPORTED_QUANTILES.map(function (quantile) {
61
+ return {
62
+ key: String(Math.round(quantile * 100)),
63
+ rank: Math.max(1, Math.ceil(quantile * observedCount))
64
+ };
65
+ });
66
+ var buckets = {};
67
+ var percentilesMs = {};
68
+ var sumMs = 0;
69
+ var counted = 0;
70
+ var _iterator = _createForOfIteratorHelper(latencies),
71
+ _step;
72
+ try {
73
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
74
+ var _this$countByLatency$, _buckets$bucket;
75
+ var latencyMs = _step.value;
76
+ var count = (_this$countByLatency$ = this.countByLatency.get(latencyMs)) !== null && _this$countByLatency$ !== void 0 ? _this$countByLatency$ : 0;
77
+ sumMs += latencyMs * count;
78
+ var bucket = String(bucketKeyForMs(latencyMs));
79
+ buckets[bucket] = ((_buckets$bucket = buckets[bucket]) !== null && _buckets$bucket !== void 0 ? _buckets$bucket : 0) + count;
80
+
81
+ // A percentile is the latency the group's interactions reach counting up from the
82
+ // fastest, so it is answered as soon as this many of them have been passed.
83
+ counted += count;
84
+ var _iterator2 = _createForOfIteratorHelper(percentileRanks),
85
+ _step2;
86
+ try {
87
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
88
+ var _step2$value = _step2.value,
89
+ key = _step2$value.key,
90
+ rank = _step2$value.rank;
91
+ if (percentilesMs[key] === undefined && counted >= rank) {
92
+ percentilesMs[key] = latencyMs;
93
+ }
94
+ }
95
+ } catch (err) {
96
+ _iterator2.e(err);
97
+ } finally {
98
+ _iterator2.f();
99
+ }
100
+ }
101
+ } catch (err) {
102
+ _iterator.e(err);
103
+ } finally {
104
+ _iterator.f();
105
+ }
106
+ return {
107
+ // `performance.interactionCount` can lag the entries the observer has delivered.
108
+ totalCount: Math.max(totalCount, observedCount),
109
+ observedCount: observedCount,
110
+ sumMs: sumMs,
111
+ maxMs: (_latencies = latencies[latencies.length - 1]) !== null && _latencies !== void 0 ? _latencies : 0,
112
+ buckets: buckets,
113
+ percentilesMs: percentilesMs
114
+ };
115
+ }
116
+ }, {
117
+ key: "observedCount",
118
+ value: function observedCount() {
119
+ var total = 0;
120
+ var _iterator3 = _createForOfIteratorHelper(this.countByLatency.values()),
121
+ _step3;
122
+ try {
123
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
124
+ var count = _step3.value;
125
+ total += count;
126
+ }
127
+ } catch (err) {
128
+ _iterator3.e(err);
129
+ } finally {
130
+ _iterator3.f();
131
+ }
132
+ return total;
133
+ }
134
+
135
+ /** Rounding lives here so that every count goes through the same step, in or out. */
136
+ }, {
137
+ key: "roundLatencyUp",
138
+ value: function roundLatencyUp(latencyMs) {
139
+ return Math.ceil(latencyMs / RESOLUTION_MS) * RESOLUTION_MS;
140
+ }
141
+ }, {
142
+ key: "increment",
143
+ value: function increment(latencyMs) {
144
+ var _this$countByLatency$2;
145
+ var step = this.roundLatencyUp(latencyMs);
146
+ this.countByLatency.set(step, ((_this$countByLatency$2 = this.countByLatency.get(step)) !== null && _this$countByLatency$2 !== void 0 ? _this$countByLatency$2 : 0) + 1);
147
+ }
148
+ }, {
149
+ key: "decrement",
150
+ value: function decrement(latencyMs) {
151
+ var _this$countByLatency$3;
152
+ var step = this.roundLatencyUp(latencyMs);
153
+ var next = ((_this$countByLatency$3 = this.countByLatency.get(step)) !== null && _this$countByLatency$3 !== void 0 ? _this$countByLatency$3 : 0) - 1;
154
+ if (next > 0) {
155
+ this.countByLatency.set(step, next);
156
+ } else {
157
+ this.countByLatency.delete(step);
158
+ }
159
+ }
160
+ }]);
161
+ }();
@@ -0,0 +1,108 @@
1
+ import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
2
+ import _createClass from "@babel/runtime/helpers/createClass";
3
+ import { REPORTING_THRESHOLD_MS } from './bucket-boundaries';
4
+ /** `performance.interactionCount` is Chromium-only and absent from the DOM typings. */
5
+ var interactionCount = function interactionCount() {
6
+ return performance.interactionCount;
7
+ };
8
+
9
+ /** `durationThreshold` is absent from the DOM typings for `PerformanceObserverInit`. */
10
+
11
+ /**
12
+ * Reports the interactions the browser observes to `onEntries`.
13
+ *
14
+ * `drain` and `stop` are safe to call before `start` and after each other, so a session
15
+ * that never started collecting needs no special handling.
16
+ */
17
+ export var InteractionObserver = /*#__PURE__*/function () {
18
+ function InteractionObserver(onEntries) {
19
+ _classCallCheck(this, InteractionObserver);
20
+ this.onEntries = onEntries;
21
+ }
22
+
23
+ /**
24
+ * Starts reporting interactions from this point on. Does nothing when already started, so
25
+ * a second call cannot leave an observer running with nobody to disconnect it.
26
+ *
27
+ * `buffered` is `false`: the entries the browser collected earlier are interactions with
28
+ * the page while the editor was still loading, and they belong to no session of ours.
29
+ */
30
+ return _createClass(InteractionObserver, [{
31
+ key: "start",
32
+ value: function start() {
33
+ var _this = this;
34
+ if (this.observer) {
35
+ return;
36
+ }
37
+ this.observer = new PerformanceObserver(function (list) {
38
+ // Delay by a microtask to work around a Safari bug where the callback is
39
+ // invoked synchronously rather than in a separate task.
40
+ // See: https://github.com/GoogleChrome/web-vitals/issues/277
41
+ Promise.resolve().then(function () {
42
+ _this.onEntries(list.getEntries());
43
+ });
44
+ });
45
+ var init = {
46
+ type: 'event',
47
+ buffered: false,
48
+ // 16 ms is also the smallest value the spec honours; lower values are clamped.
49
+ durationThreshold: REPORTING_THRESHOLD_MS
50
+ };
51
+ this.observer.observe(init);
52
+ }
53
+
54
+ /**
55
+ * Synchronously reports the entries the browser has produced but not yet dispatched to
56
+ * the callback. A snapshot taken because the page is going away has to include them,
57
+ * because there is no later chance to.
58
+ */
59
+ }, {
60
+ key: "drain",
61
+ value: function drain() {
62
+ var _this$observer;
63
+ var entries = (_this$observer = this.observer) === null || _this$observer === void 0 ? void 0 : _this$observer.takeRecords();
64
+ if (entries) {
65
+ this.onEntries(entries);
66
+ }
67
+ }
68
+ }, {
69
+ key: "stop",
70
+ value: function stop() {
71
+ var _this$observer2;
72
+ (_this$observer2 = this.observer) === null || _this$observer2 === void 0 || _this$observer2.disconnect();
73
+ this.observer = undefined;
74
+ }
75
+ }], [{
76
+ key: "isSupported",
77
+ value:
78
+ /**
79
+ * Whether the browser reports both things a session needs: `interactionId`, which groups
80
+ * entries into interactions, and `performance.interactionCount`, which counts the ones
81
+ * below the reporting threshold. Both are Chromium-only, and without the count
82
+ * `totalCount` would be indistinguishable from `observedCount`.
83
+ */
84
+ function isSupported() {
85
+ if (typeof window === 'undefined' || typeof PerformanceObserver === 'undefined') {
86
+ return false;
87
+ }
88
+ if (!('PerformanceEventTiming' in window) || !('interactionId' in PerformanceEventTiming.prototype)) {
89
+ return false;
90
+ }
91
+ if (!PerformanceObserver.supportedEntryTypes.includes('event')) {
92
+ return false;
93
+ }
94
+ return typeof interactionCount() === 'number';
95
+ }
96
+
97
+ /**
98
+ * Total interactions on the page since page load, including those below the reporting
99
+ * threshold that no observer ever sees. A property of the page, not of an observer.
100
+ */
101
+ }, {
102
+ key: "readPageInteractionCount",
103
+ value: function readPageInteractionCount() {
104
+ var _interactionCount;
105
+ return (_interactionCount = interactionCount()) !== null && _interactionCount !== void 0 ? _interactionCount : 0;
106
+ }
107
+ }]);
108
+ }();
@@ -0,0 +1,137 @@
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
+ 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; } } }; }
5
+ 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; } }
6
+ 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; }
7
+ /**
8
+ * The Event Timing fields this package reads. `interactionId` is missing from the DOM
9
+ * typings' `PerformanceEventTiming`, so it is declared here as optional, which also makes
10
+ * a `PerformanceEntry` from `getEntries()` assignable without a cast.
11
+ */
12
+
13
+ /**
14
+ * What an entry did to the interaction it belongs to: either it is the first entry of a new
15
+ * interaction, or it measured an interaction that was already counted as slower than it was
16
+ * known to be.
17
+ */
18
+
19
+ /**
20
+ * How many interactions are remembered, so their growth can still be applied. Entries of one
21
+ * interaction arrive within the interaction itself, so anything older is not needed.
22
+ */
23
+ var MAX_TRACKED = 256;
24
+ /**
25
+ * How many are dropped per cleanup. Dropping one at a time would run a cleanup on every new
26
+ * interaction once the limit is reached; a batch makes it one cleanup per 64 of them.
27
+ */
28
+ var PRUNE_BATCH = 64;
29
+
30
+ /**
31
+ * Groups Event Timing entries into interactions.
32
+ *
33
+ * Entries sharing a non-zero `interactionId` are one interaction whose latency is the
34
+ * maximum `duration` among them. Entries arrive incrementally, so an interaction's latency
35
+ * can grow after it was first reported — callers apply that to what they already counted
36
+ * rather than counting the interaction twice.
37
+ *
38
+ * A non-zero `interactionId` is the browser's own definition of an interaction, which is
39
+ * also what INP filters on: it is assigned to the pointer and keyboard events that make one
40
+ * up, and never to scrolling or pointer movement.
41
+ */
42
+ export var InteractionTracker = /*#__PURE__*/function () {
43
+ /**
44
+ * @param startsAfterInteractionId interactions up to and including this one belong to the
45
+ * previous tracker and are ignored. `interactionId` counts up over the life of the page, so
46
+ * a session opening mid-page passes the highest id the one before it saw; without that, an
47
+ * entry still arriving for an interaction from the previous session would look new here and
48
+ * be counted in both.
49
+ */
50
+ function InteractionTracker() {
51
+ var startsAfterInteractionId = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
52
+ _classCallCheck(this, InteractionTracker);
53
+ _defineProperty(this, "latencies", new Map());
54
+ _defineProperty(this, "highestInteractionId", 0);
55
+ this.startsAfterInteractionId = startsAfterInteractionId;
56
+ }
57
+
58
+ /** The highest `interactionId` this tracker has seen. */
59
+ return _createClass(InteractionTracker, [{
60
+ key: "lastInteractionId",
61
+ get: function get() {
62
+ return Math.max(this.highestInteractionId, this.startsAfterInteractionId);
63
+ }
64
+
65
+ /**
66
+ * Merges an entry into the interaction it belongs to.
67
+ *
68
+ * @returns what that did to the interaction's latency, or nothing when the entry is not
69
+ * part of an interaction, belongs to a previous tracker, or does not change one.
70
+ */
71
+ }, {
72
+ key: "merge",
73
+ value: function merge(entry) {
74
+ var interactionId = entry.interactionId;
75
+ // `first-input` and non-interaction events report `interactionId` 0.
76
+ if (!interactionId) {
77
+ return undefined;
78
+ }
79
+ if (interactionId <= this.startsAfterInteractionId) {
80
+ return undefined;
81
+ }
82
+ this.highestInteractionId = Math.max(this.highestInteractionId, interactionId);
83
+
84
+ // Every latency is counted through here, so this is where one that cannot be measured
85
+ // is rejected: a `NaN` getting through becomes a `NaN` bucket key and a `NaN` `sumMs`
86
+ // for the rest of the session.
87
+ if (!Number.isFinite(entry.duration) || entry.duration < 0) {
88
+ return undefined;
89
+ }
90
+ var previousLatencyMs = this.latencies.get(interactionId);
91
+ if (previousLatencyMs === undefined) {
92
+ this.latencies.set(interactionId, entry.duration);
93
+ this.prune();
94
+ return {
95
+ type: 'new',
96
+ latencyMs: entry.duration
97
+ };
98
+ }
99
+ if (entry.duration <= previousLatencyMs) {
100
+ return undefined;
101
+ }
102
+ this.latencies.set(interactionId, entry.duration);
103
+ return {
104
+ type: 'remeasured',
105
+ fromMs: previousLatencyMs,
106
+ toMs: entry.duration
107
+ };
108
+ }
109
+ }, {
110
+ key: "prune",
111
+ value: function prune() {
112
+ if (this.latencies.size <= MAX_TRACKED) {
113
+ return;
114
+ }
115
+
116
+ // `Map` preserves insertion order and `interactionId` increases monotonically,
117
+ // so the entries inserted first are the least likely to see another entry.
118
+ var remaining = PRUNE_BATCH;
119
+ var _iterator = _createForOfIteratorHelper(this.latencies.keys()),
120
+ _step;
121
+ try {
122
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
123
+ var interactionId = _step.value;
124
+ this.latencies.delete(interactionId);
125
+ remaining -= 1;
126
+ if (remaining === 0) {
127
+ return;
128
+ }
129
+ }
130
+ } catch (err) {
131
+ _iterator.e(err);
132
+ } finally {
133
+ _iterator.f();
134
+ }
135
+ }
136
+ }]);
137
+ }();
@@ -0,0 +1,282 @@
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
+ 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; } } }; }
5
+ 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; } }
6
+ 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; }
7
+ import { getDocument } from '@atlaskit/browser-apis';
8
+ import { InteractionObserver } from './interaction-observer';
9
+ import { InteractivitySession } from './interactivity-session';
10
+ import { SCHEMA_VERSION } from './bucket-boundaries';
11
+ import { LifecycleObserver } from './lifecycle-observer';
12
+ import { SnapshotScheduler } from './snapshot-scheduler';
13
+ /**
14
+ * Collects interaction latencies for one editor mount and emits session-to-date snapshots.
15
+ *
16
+ * `start` and `stop` bound the collecting, which happens once. Within it there can be several
17
+ * sessions, because a session covers one document in one mode: a Confluence live page
18
+ * navigates and switches between reading and editing without ever remounting the editor, and
19
+ * each of those closes the current session and opens the next.
20
+ */
21
+ export var InteractivityCollector = /*#__PURE__*/function () {
22
+ function InteractivityCollector(options) {
23
+ var _this = this;
24
+ _classCallCheck(this, InteractivityCollector);
25
+ _defineProperty(this, "started", false);
26
+ _defineProperty(this, "stopped", false);
27
+ this.emit = options.emit;
28
+ this.getEditorDomSize = options.getEditorDomSize;
29
+ this.getNodeSize = options.getNodeSize;
30
+ this.getObjectId = options.getObjectId;
31
+ this.getSessionMode = options.getSessionMode;
32
+ this.interactionObserver = new InteractionObserver(function (entries) {
33
+ return _this.recordEntries(entries);
34
+ });
35
+ this.snapshotScheduler = new SnapshotScheduler(function () {
36
+ return _this.onTimer();
37
+ });
38
+ this.lifecycleObserver = new LifecycleObserver({
39
+ onHidden: function onHidden() {
40
+ return _this.onHidden();
41
+ },
42
+ onVisible: function onVisible() {
43
+ return _this.onVisible();
44
+ },
45
+ onPageHide: function onPageHide() {
46
+ return _this.onPageHide();
47
+ },
48
+ onPageShow: function onPageShow() {
49
+ return _this.onPageShow();
50
+ }
51
+ });
52
+ this.session = this.createSession(0);
53
+ }
54
+
55
+ /**
56
+ * Starts collecting. Calling it again while collecting changes nothing.
57
+ *
58
+ * @returns whether collecting is running; `false` when the browser cannot support it, or
59
+ * when it has already been stopped.
60
+ */
61
+ return _createClass(InteractivityCollector, [{
62
+ key: "start",
63
+ value: function start() {
64
+ // Collecting is over for good once stopped: the session it covered has been reported.
65
+ if (this.stopped) {
66
+ return false;
67
+ }
68
+ if (this.started) {
69
+ return true;
70
+ }
71
+ if (!InteractionObserver.isSupported()) {
72
+ return false;
73
+ }
74
+
75
+ // Opened again so the session's timings start with the collecting, not with whenever
76
+ // this object was constructed.
77
+ this.session = this.createSession(0);
78
+ this.interactionObserver.start();
79
+ this.snapshotScheduler.start();
80
+ this.lifecycleObserver.start();
81
+ this.started = true;
82
+ return true;
83
+ }
84
+
85
+ /** Reports the session as ended by the editor unmounting, and stops collecting. */
86
+ }, {
87
+ key: "stop",
88
+ value: function stop() {
89
+ if (this.stopped) {
90
+ return;
91
+ }
92
+ this.takeSnapshot('unmount');
93
+ this.stopped = true;
94
+ this.interactionObserver.stop();
95
+ this.snapshotScheduler.stop();
96
+ this.lifecycleObserver.stop();
97
+ }
98
+
99
+ /**
100
+ * Rotates the session when the editor is pointed at different content.
101
+ *
102
+ * An unknown object id is no information, never a change. The provider resolves
103
+ * asynchronously after mount, and `contextIdentifierPlugin` resets its state to the
104
+ * configured provider on transactions that do not carry a new one, so the id read here goes
105
+ * missing for a moment on a document that never changed.
106
+ */
107
+ }, {
108
+ key: "onObjectIdChanged",
109
+ value: function onObjectIdChanged() {
110
+ if (this.stopped) {
111
+ return;
112
+ }
113
+ var objectId = this.getObjectId();
114
+ if (objectId === undefined || objectId === this.session.objectId) {
115
+ return;
116
+ }
117
+ if (this.session.objectId === undefined) {
118
+ this.session.objectId = objectId;
119
+ return;
120
+ }
121
+ this.rotateSession('navigation');
122
+ }
123
+
124
+ /**
125
+ * Rotates the session when the editor switches between reading and editing. Interactions
126
+ * with a read-only page are a different population from interactions while editing, so one
127
+ * session never covers both.
128
+ */
129
+ }, {
130
+ key: "onViewModeChanged",
131
+ value: function onViewModeChanged() {
132
+ if (this.stopped) {
133
+ return;
134
+ }
135
+ var mode = this.getSessionMode();
136
+ if (mode === undefined || mode === this.session.mode) {
137
+ return;
138
+ }
139
+ if (this.session.mode === undefined) {
140
+ this.session.mode = mode;
141
+ return;
142
+ }
143
+ this.rotateSession('modeChange');
144
+ }
145
+
146
+ /**
147
+ * @param startsAfterInteractionId the highest interaction the previous session saw, so
148
+ * entries still arriving for it are not counted here as well.
149
+ */
150
+ }, {
151
+ key: "createSession",
152
+ value: function createSession(startsAfterInteractionId) {
153
+ var _getDocument;
154
+ return new InteractivitySession({
155
+ objectId: this.getObjectId(),
156
+ mode: this.getSessionMode(),
157
+ hidden: ((_getDocument = getDocument()) === null || _getDocument === void 0 ? void 0 : _getDocument.visibilityState) === 'hidden',
158
+ startsAfterInteractionId: startsAfterInteractionId
159
+ });
160
+ }
161
+
162
+ /** Reports the current session as ended by `reason` and puts a new one in its place. */
163
+ }, {
164
+ key: "rotateSession",
165
+ value: function rotateSession(reason) {
166
+ this.takeSnapshot(reason);
167
+ this.session = this.createSession(this.session.tracker.lastInteractionId);
168
+ this.snapshotScheduler.restart();
169
+ }
170
+ }, {
171
+ key: "onTimer",
172
+ value: function onTimer() {
173
+ this.takeSnapshot('timer');
174
+ }
175
+ }, {
176
+ key: "onHidden",
177
+ value: function onHidden() {
178
+ if (this.session.hiddenSince === undefined) {
179
+ this.session.hiddenSince = performance.now();
180
+ }
181
+ this.takeSnapshot('hidden');
182
+ }
183
+ }, {
184
+ key: "onVisible",
185
+ value: function onVisible() {
186
+ if (this.session.hiddenSince !== undefined) {
187
+ this.session.hiddenMs += performance.now() - this.session.hiddenSince;
188
+ this.session.hiddenSince = undefined;
189
+ }
190
+ this.session.lifecycleSnapshotEmitted = false;
191
+ }
192
+ }, {
193
+ key: "onPageHide",
194
+ value: function onPageHide() {
195
+ this.takeSnapshot('pagehide');
196
+ }
197
+
198
+ /**
199
+ * The page came back from the back/forward cache, which never raises a visibility change.
200
+ * The session continues, and the signal that suspended it has been reported, so the next
201
+ * one is due.
202
+ */
203
+ }, {
204
+ key: "onPageShow",
205
+ value: function onPageShow() {
206
+ this.session.lifecycleSnapshotEmitted = false;
207
+ }
208
+ }, {
209
+ key: "recordEntries",
210
+ value: function recordEntries(entries) {
211
+ var _this$session = this.session,
212
+ tracker = _this$session.tracker,
213
+ page = _this$session.page;
214
+ var _iterator = _createForOfIteratorHelper(entries),
215
+ _step;
216
+ try {
217
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
218
+ var entry = _step.value;
219
+ var update = tracker.merge(entry);
220
+ if (!update) {
221
+ continue;
222
+ }
223
+ if (update.type === 'new') {
224
+ page.add(update.latencyMs);
225
+ } else {
226
+ page.remeasure(update.fromMs, update.toMs);
227
+ }
228
+ this.session.revision += 1;
229
+ }
230
+ } catch (err) {
231
+ _iterator.e(err);
232
+ } finally {
233
+ _iterator.f();
234
+ }
235
+ }
236
+ }, {
237
+ key: "takeSnapshot",
238
+ value: function takeSnapshot(reason) {
239
+ if (this.stopped) {
240
+ return;
241
+ }
242
+ var session = this.session;
243
+
244
+ // Checked before anything is consumed: `visibilitychange` and `pagehide` fire back to
245
+ // back on the same transition and it is reported once, and draining first would take
246
+ // entries out of the browser's queue only to suppress the snapshot carrying them.
247
+ var lifecycleSignal = reason === 'hidden' || reason === 'pagehide';
248
+ if (lifecycleSignal && session.lifecycleSnapshotEmitted) {
249
+ return;
250
+ }
251
+
252
+ // Must run before the change check: the browser may be holding entries that have not
253
+ // reached the observer callback yet, and on `pagehide` there is no later chance to
254
+ // pick them up.
255
+ this.interactionObserver.drain();
256
+ if (session.revision === session.emittedRevision) {
257
+ return;
258
+ }
259
+ if (lifecycleSignal) {
260
+ session.lifecycleSnapshotEmitted = true;
261
+ }
262
+ session.seq += 1;
263
+ session.emittedRevision = session.revision;
264
+ var now = performance.now();
265
+ var hiddenMs = session.hiddenMs + (session.hiddenSince === undefined ? 0 : now - session.hiddenSince);
266
+ var totalCount = InteractionObserver.readPageInteractionCount() - session.interactionCountAtStart;
267
+ this.emit({
268
+ schema: SCHEMA_VERSION,
269
+ interactivitySessionId: session.id,
270
+ objectId: session.objectId,
271
+ seq: session.seq,
272
+ reason: reason,
273
+ sessionMode: session.mode,
274
+ activeMs: Math.round(Math.max(0, now - session.startedAt - hiddenMs)),
275
+ hiddenMs: Math.round(hiddenMs),
276
+ nodeSize: this.getNodeSize(),
277
+ editorDomSize: this.getEditorDomSize(),
278
+ page: session.page.snapshot(totalCount)
279
+ });
280
+ }
281
+ }]);
282
+ }();