@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,144 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.InteractionTracker = 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
+ 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; } } }; }
12
+ 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; } }
13
+ 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; }
14
+ /**
15
+ * The Event Timing fields this package reads. `interactionId` is missing from the DOM
16
+ * typings' `PerformanceEventTiming`, so it is declared here as optional, which also makes
17
+ * a `PerformanceEntry` from `getEntries()` assignable without a cast.
18
+ */
19
+
20
+ /**
21
+ * What an entry did to the interaction it belongs to: either it is the first entry of a new
22
+ * interaction, or it measured an interaction that was already counted as slower than it was
23
+ * known to be.
24
+ */
25
+
26
+ /**
27
+ * How many interactions are remembered, so their growth can still be applied. Entries of one
28
+ * interaction arrive within the interaction itself, so anything older is not needed.
29
+ */
30
+ var MAX_TRACKED = 256;
31
+ /**
32
+ * How many are dropped per cleanup. Dropping one at a time would run a cleanup on every new
33
+ * interaction once the limit is reached; a batch makes it one cleanup per 64 of them.
34
+ */
35
+ var PRUNE_BATCH = 64;
36
+
37
+ /**
38
+ * Groups Event Timing entries into interactions.
39
+ *
40
+ * Entries sharing a non-zero `interactionId` are one interaction whose latency is the
41
+ * maximum `duration` among them. Entries arrive incrementally, so an interaction's latency
42
+ * can grow after it was first reported — callers apply that to what they already counted
43
+ * rather than counting the interaction twice.
44
+ *
45
+ * A non-zero `interactionId` is the browser's own definition of an interaction, which is
46
+ * also what INP filters on: it is assigned to the pointer and keyboard events that make one
47
+ * up, and never to scrolling or pointer movement.
48
+ */
49
+ var InteractionTracker = exports.InteractionTracker = /*#__PURE__*/function () {
50
+ /**
51
+ * @param startsAfterInteractionId interactions up to and including this one belong to the
52
+ * previous tracker and are ignored. `interactionId` counts up over the life of the page, so
53
+ * a session opening mid-page passes the highest id the one before it saw; without that, an
54
+ * entry still arriving for an interaction from the previous session would look new here and
55
+ * be counted in both.
56
+ */
57
+ function InteractionTracker() {
58
+ var startsAfterInteractionId = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
59
+ (0, _classCallCheck2.default)(this, InteractionTracker);
60
+ (0, _defineProperty2.default)(this, "latencies", new Map());
61
+ (0, _defineProperty2.default)(this, "highestInteractionId", 0);
62
+ this.startsAfterInteractionId = startsAfterInteractionId;
63
+ }
64
+
65
+ /** The highest `interactionId` this tracker has seen. */
66
+ return (0, _createClass2.default)(InteractionTracker, [{
67
+ key: "lastInteractionId",
68
+ get: function get() {
69
+ return Math.max(this.highestInteractionId, this.startsAfterInteractionId);
70
+ }
71
+
72
+ /**
73
+ * Merges an entry into the interaction it belongs to.
74
+ *
75
+ * @returns what that did to the interaction's latency, or nothing when the entry is not
76
+ * part of an interaction, belongs to a previous tracker, or does not change one.
77
+ */
78
+ }, {
79
+ key: "merge",
80
+ value: function merge(entry) {
81
+ var interactionId = entry.interactionId;
82
+ // `first-input` and non-interaction events report `interactionId` 0.
83
+ if (!interactionId) {
84
+ return undefined;
85
+ }
86
+ if (interactionId <= this.startsAfterInteractionId) {
87
+ return undefined;
88
+ }
89
+ this.highestInteractionId = Math.max(this.highestInteractionId, interactionId);
90
+
91
+ // Every latency is counted through here, so this is where one that cannot be measured
92
+ // is rejected: a `NaN` getting through becomes a `NaN` bucket key and a `NaN` `sumMs`
93
+ // for the rest of the session.
94
+ if (!Number.isFinite(entry.duration) || entry.duration < 0) {
95
+ return undefined;
96
+ }
97
+ var previousLatencyMs = this.latencies.get(interactionId);
98
+ if (previousLatencyMs === undefined) {
99
+ this.latencies.set(interactionId, entry.duration);
100
+ this.prune();
101
+ return {
102
+ type: 'new',
103
+ latencyMs: entry.duration
104
+ };
105
+ }
106
+ if (entry.duration <= previousLatencyMs) {
107
+ return undefined;
108
+ }
109
+ this.latencies.set(interactionId, entry.duration);
110
+ return {
111
+ type: 'remeasured',
112
+ fromMs: previousLatencyMs,
113
+ toMs: entry.duration
114
+ };
115
+ }
116
+ }, {
117
+ key: "prune",
118
+ value: function prune() {
119
+ if (this.latencies.size <= MAX_TRACKED) {
120
+ return;
121
+ }
122
+
123
+ // `Map` preserves insertion order and `interactionId` increases monotonically,
124
+ // so the entries inserted first are the least likely to see another entry.
125
+ var remaining = PRUNE_BATCH;
126
+ var _iterator = _createForOfIteratorHelper(this.latencies.keys()),
127
+ _step;
128
+ try {
129
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
130
+ var interactionId = _step.value;
131
+ this.latencies.delete(interactionId);
132
+ remaining -= 1;
133
+ if (remaining === 0) {
134
+ return;
135
+ }
136
+ }
137
+ } catch (err) {
138
+ _iterator.e(err);
139
+ } finally {
140
+ _iterator.f();
141
+ }
142
+ }
143
+ }]);
144
+ }();
@@ -0,0 +1,289 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.InteractivityCollector = 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 _browserApis = require("@atlaskit/browser-apis");
12
+ var _interactionObserver = require("./interaction-observer");
13
+ var _interactivitySession = require("./interactivity-session");
14
+ var _bucketBoundaries = require("./bucket-boundaries");
15
+ var _lifecycleObserver = require("./lifecycle-observer");
16
+ var _snapshotScheduler = require("./snapshot-scheduler");
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; } } }; }
18
+ 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; } }
19
+ 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; }
20
+ /**
21
+ * Collects interaction latencies for one editor mount and emits session-to-date snapshots.
22
+ *
23
+ * `start` and `stop` bound the collecting, which happens once. Within it there can be several
24
+ * sessions, because a session covers one document in one mode: a Confluence live page
25
+ * navigates and switches between reading and editing without ever remounting the editor, and
26
+ * each of those closes the current session and opens the next.
27
+ */
28
+ var InteractivityCollector = exports.InteractivityCollector = /*#__PURE__*/function () {
29
+ function InteractivityCollector(options) {
30
+ var _this = this;
31
+ (0, _classCallCheck2.default)(this, InteractivityCollector);
32
+ (0, _defineProperty2.default)(this, "started", false);
33
+ (0, _defineProperty2.default)(this, "stopped", false);
34
+ this.emit = options.emit;
35
+ this.getEditorDomSize = options.getEditorDomSize;
36
+ this.getNodeSize = options.getNodeSize;
37
+ this.getObjectId = options.getObjectId;
38
+ this.getSessionMode = options.getSessionMode;
39
+ this.interactionObserver = new _interactionObserver.InteractionObserver(function (entries) {
40
+ return _this.recordEntries(entries);
41
+ });
42
+ this.snapshotScheduler = new _snapshotScheduler.SnapshotScheduler(function () {
43
+ return _this.onTimer();
44
+ });
45
+ this.lifecycleObserver = new _lifecycleObserver.LifecycleObserver({
46
+ onHidden: function onHidden() {
47
+ return _this.onHidden();
48
+ },
49
+ onVisible: function onVisible() {
50
+ return _this.onVisible();
51
+ },
52
+ onPageHide: function onPageHide() {
53
+ return _this.onPageHide();
54
+ },
55
+ onPageShow: function onPageShow() {
56
+ return _this.onPageShow();
57
+ }
58
+ });
59
+ this.session = this.createSession(0);
60
+ }
61
+
62
+ /**
63
+ * Starts collecting. Calling it again while collecting changes nothing.
64
+ *
65
+ * @returns whether collecting is running; `false` when the browser cannot support it, or
66
+ * when it has already been stopped.
67
+ */
68
+ return (0, _createClass2.default)(InteractivityCollector, [{
69
+ key: "start",
70
+ value: function start() {
71
+ // Collecting is over for good once stopped: the session it covered has been reported.
72
+ if (this.stopped) {
73
+ return false;
74
+ }
75
+ if (this.started) {
76
+ return true;
77
+ }
78
+ if (!_interactionObserver.InteractionObserver.isSupported()) {
79
+ return false;
80
+ }
81
+
82
+ // Opened again so the session's timings start with the collecting, not with whenever
83
+ // this object was constructed.
84
+ this.session = this.createSession(0);
85
+ this.interactionObserver.start();
86
+ this.snapshotScheduler.start();
87
+ this.lifecycleObserver.start();
88
+ this.started = true;
89
+ return true;
90
+ }
91
+
92
+ /** Reports the session as ended by the editor unmounting, and stops collecting. */
93
+ }, {
94
+ key: "stop",
95
+ value: function stop() {
96
+ if (this.stopped) {
97
+ return;
98
+ }
99
+ this.takeSnapshot('unmount');
100
+ this.stopped = true;
101
+ this.interactionObserver.stop();
102
+ this.snapshotScheduler.stop();
103
+ this.lifecycleObserver.stop();
104
+ }
105
+
106
+ /**
107
+ * Rotates the session when the editor is pointed at different content.
108
+ *
109
+ * An unknown object id is no information, never a change. The provider resolves
110
+ * asynchronously after mount, and `contextIdentifierPlugin` resets its state to the
111
+ * configured provider on transactions that do not carry a new one, so the id read here goes
112
+ * missing for a moment on a document that never changed.
113
+ */
114
+ }, {
115
+ key: "onObjectIdChanged",
116
+ value: function onObjectIdChanged() {
117
+ if (this.stopped) {
118
+ return;
119
+ }
120
+ var objectId = this.getObjectId();
121
+ if (objectId === undefined || objectId === this.session.objectId) {
122
+ return;
123
+ }
124
+ if (this.session.objectId === undefined) {
125
+ this.session.objectId = objectId;
126
+ return;
127
+ }
128
+ this.rotateSession('navigation');
129
+ }
130
+
131
+ /**
132
+ * Rotates the session when the editor switches between reading and editing. Interactions
133
+ * with a read-only page are a different population from interactions while editing, so one
134
+ * session never covers both.
135
+ */
136
+ }, {
137
+ key: "onViewModeChanged",
138
+ value: function onViewModeChanged() {
139
+ if (this.stopped) {
140
+ return;
141
+ }
142
+ var mode = this.getSessionMode();
143
+ if (mode === undefined || mode === this.session.mode) {
144
+ return;
145
+ }
146
+ if (this.session.mode === undefined) {
147
+ this.session.mode = mode;
148
+ return;
149
+ }
150
+ this.rotateSession('modeChange');
151
+ }
152
+
153
+ /**
154
+ * @param startsAfterInteractionId the highest interaction the previous session saw, so
155
+ * entries still arriving for it are not counted here as well.
156
+ */
157
+ }, {
158
+ key: "createSession",
159
+ value: function createSession(startsAfterInteractionId) {
160
+ var _getDocument;
161
+ return new _interactivitySession.InteractivitySession({
162
+ objectId: this.getObjectId(),
163
+ mode: this.getSessionMode(),
164
+ hidden: ((_getDocument = (0, _browserApis.getDocument)()) === null || _getDocument === void 0 ? void 0 : _getDocument.visibilityState) === 'hidden',
165
+ startsAfterInteractionId: startsAfterInteractionId
166
+ });
167
+ }
168
+
169
+ /** Reports the current session as ended by `reason` and puts a new one in its place. */
170
+ }, {
171
+ key: "rotateSession",
172
+ value: function rotateSession(reason) {
173
+ this.takeSnapshot(reason);
174
+ this.session = this.createSession(this.session.tracker.lastInteractionId);
175
+ this.snapshotScheduler.restart();
176
+ }
177
+ }, {
178
+ key: "onTimer",
179
+ value: function onTimer() {
180
+ this.takeSnapshot('timer');
181
+ }
182
+ }, {
183
+ key: "onHidden",
184
+ value: function onHidden() {
185
+ if (this.session.hiddenSince === undefined) {
186
+ this.session.hiddenSince = performance.now();
187
+ }
188
+ this.takeSnapshot('hidden');
189
+ }
190
+ }, {
191
+ key: "onVisible",
192
+ value: function onVisible() {
193
+ if (this.session.hiddenSince !== undefined) {
194
+ this.session.hiddenMs += performance.now() - this.session.hiddenSince;
195
+ this.session.hiddenSince = undefined;
196
+ }
197
+ this.session.lifecycleSnapshotEmitted = false;
198
+ }
199
+ }, {
200
+ key: "onPageHide",
201
+ value: function onPageHide() {
202
+ this.takeSnapshot('pagehide');
203
+ }
204
+
205
+ /**
206
+ * The page came back from the back/forward cache, which never raises a visibility change.
207
+ * The session continues, and the signal that suspended it has been reported, so the next
208
+ * one is due.
209
+ */
210
+ }, {
211
+ key: "onPageShow",
212
+ value: function onPageShow() {
213
+ this.session.lifecycleSnapshotEmitted = false;
214
+ }
215
+ }, {
216
+ key: "recordEntries",
217
+ value: function recordEntries(entries) {
218
+ var _this$session = this.session,
219
+ tracker = _this$session.tracker,
220
+ page = _this$session.page;
221
+ var _iterator = _createForOfIteratorHelper(entries),
222
+ _step;
223
+ try {
224
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
225
+ var entry = _step.value;
226
+ var update = tracker.merge(entry);
227
+ if (!update) {
228
+ continue;
229
+ }
230
+ if (update.type === 'new') {
231
+ page.add(update.latencyMs);
232
+ } else {
233
+ page.remeasure(update.fromMs, update.toMs);
234
+ }
235
+ this.session.revision += 1;
236
+ }
237
+ } catch (err) {
238
+ _iterator.e(err);
239
+ } finally {
240
+ _iterator.f();
241
+ }
242
+ }
243
+ }, {
244
+ key: "takeSnapshot",
245
+ value: function takeSnapshot(reason) {
246
+ if (this.stopped) {
247
+ return;
248
+ }
249
+ var session = this.session;
250
+
251
+ // Checked before anything is consumed: `visibilitychange` and `pagehide` fire back to
252
+ // back on the same transition and it is reported once, and draining first would take
253
+ // entries out of the browser's queue only to suppress the snapshot carrying them.
254
+ var lifecycleSignal = reason === 'hidden' || reason === 'pagehide';
255
+ if (lifecycleSignal && session.lifecycleSnapshotEmitted) {
256
+ return;
257
+ }
258
+
259
+ // Must run before the change check: the browser may be holding entries that have not
260
+ // reached the observer callback yet, and on `pagehide` there is no later chance to
261
+ // pick them up.
262
+ this.interactionObserver.drain();
263
+ if (session.revision === session.emittedRevision) {
264
+ return;
265
+ }
266
+ if (lifecycleSignal) {
267
+ session.lifecycleSnapshotEmitted = true;
268
+ }
269
+ session.seq += 1;
270
+ session.emittedRevision = session.revision;
271
+ var now = performance.now();
272
+ var hiddenMs = session.hiddenMs + (session.hiddenSince === undefined ? 0 : now - session.hiddenSince);
273
+ var totalCount = _interactionObserver.InteractionObserver.readPageInteractionCount() - session.interactionCountAtStart;
274
+ this.emit({
275
+ schema: _bucketBoundaries.SCHEMA_VERSION,
276
+ interactivitySessionId: session.id,
277
+ objectId: session.objectId,
278
+ seq: session.seq,
279
+ reason: reason,
280
+ sessionMode: session.mode,
281
+ activeMs: Math.round(Math.max(0, now - session.startedAt - hiddenMs)),
282
+ hiddenMs: Math.round(hiddenMs),
283
+ nodeSize: this.getNodeSize(),
284
+ editorDomSize: this.getEditorDomSize(),
285
+ page: session.page.snapshot(totalCount)
286
+ });
287
+ }
288
+ }]);
289
+ }();
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.InteractivitySession = void 0;
8
+ var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
9
+ var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
10
+ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
11
+ var _interactionObserver = require("./interaction-observer");
12
+ var _interactionTracker = require("./interaction-tracker");
13
+ var _interactionGroup = require("./interaction-group");
14
+ function createSessionId() {
15
+ if (typeof crypto.randomUUID === 'function') {
16
+ return crypto.randomUUID();
17
+ }
18
+ var bytes = new Uint8Array(16);
19
+ crypto.getRandomValues(bytes);
20
+ return Array.from(bytes, function (byte) {
21
+ return byte.toString(16).padStart(2, '0');
22
+ }).join('');
23
+ }
24
+ /**
25
+ * Everything that belongs to one session.
26
+ *
27
+ * Nothing here is reset — the next session is a new instance — so a field added here cannot
28
+ * be left carrying the previous session's value.
29
+ */
30
+ var InteractivitySession = exports.InteractivitySession = /*#__PURE__*/(0, _createClass2.default)(function InteractivitySession(start) {
31
+ (0, _classCallCheck2.default)(this, InteractivitySession);
32
+ (0, _defineProperty2.default)(this, "id", createSessionId());
33
+ (0, _defineProperty2.default)(this, "startedAt", performance.now());
34
+ /** Page interaction count when the session opened, subtracted to get its own total. */
35
+ (0, _defineProperty2.default)(this, "interactionCountAtStart", _interactionObserver.InteractionObserver.readPageInteractionCount());
36
+ (0, _defineProperty2.default)(this, "page", new _interactionGroup.InteractionGroup());
37
+ /** Increments per snapshot; a query takes the highest one per session. */
38
+ (0, _defineProperty2.default)(this, "seq", 0);
39
+ (0, _defineProperty2.default)(this, "hiddenMs", 0);
40
+ /**
41
+ * Bumped on every change to the accumulated data. A snapshot is emitted only when this
42
+ * has moved past `emittedRevision`, so identical snapshots are never sent twice.
43
+ */
44
+ (0, _defineProperty2.default)(this, "revision", 0);
45
+ (0, _defineProperty2.default)(this, "emittedRevision", 0);
46
+ /** One lifecycle snapshot per hidden episode; cleared when the page comes back. */
47
+ (0, _defineProperty2.default)(this, "lifecycleSnapshotEmitted", false);
48
+ this.objectId = start.objectId;
49
+ this.mode = start.mode;
50
+ this.hiddenSince = start.hidden ? this.startedAt : undefined;
51
+ this.tracker = new _interactionTracker.InteractionTracker(start.startsAfterInteractionId);
52
+ });
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.LifecycleObserver = void 0;
8
+ var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
9
+ var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
10
+ var _bindEventListener = require("bind-event-listener");
11
+ var _browserApis = require("@atlaskit/browser-apis");
12
+ /**
13
+ * Reports the page lifecycle signals that force an extra snapshot: the tab being hidden or
14
+ * shown again, and the page being unloaded or restored from the back/forward cache.
15
+ *
16
+ * None of them is guaranteed to arrive — a mobile browser being killed raises nothing at all.
17
+ * Snapshots carry session-to-date values, so losing the last one costs only the tail of that
18
+ * session.
19
+ */
20
+ var LifecycleObserver = exports.LifecycleObserver = /*#__PURE__*/function () {
21
+ function LifecycleObserver(handlers) {
22
+ (0, _classCallCheck2.default)(this, LifecycleObserver);
23
+ this.handlers = handlers;
24
+ }
25
+ return (0, _createClass2.default)(LifecycleObserver, [{
26
+ key: "start",
27
+ value: function start() {
28
+ var _this = this;
29
+ var doc = (0, _browserApis.getDocument)();
30
+ var unbindVisibilityChange = doc ? (0, _bindEventListener.bind)(doc, {
31
+ type: 'visibilitychange',
32
+ listener: function listener() {
33
+ if (doc.visibilityState === 'hidden') {
34
+ _this.handlers.onHidden();
35
+ } else {
36
+ _this.handlers.onVisible();
37
+ }
38
+ }
39
+ }) : undefined;
40
+ var unbindPageHide = (0, _bindEventListener.bind)(window, {
41
+ type: 'pagehide',
42
+ listener: function listener() {
43
+ return _this.handlers.onPageHide();
44
+ }
45
+ });
46
+ var unbindPageShow = (0, _bindEventListener.bind)(window, {
47
+ type: 'pageshow',
48
+ listener: function listener() {
49
+ return _this.handlers.onPageShow();
50
+ }
51
+ });
52
+ this.unbind = function () {
53
+ unbindVisibilityChange === null || unbindVisibilityChange === void 0 || unbindVisibilityChange();
54
+ unbindPageHide();
55
+ unbindPageShow();
56
+ };
57
+ }
58
+ }, {
59
+ key: "stop",
60
+ value: function stop() {
61
+ var _this$unbind;
62
+ (_this$unbind = this.unbind) === null || _this$unbind === void 0 || _this$unbind.call(this);
63
+ this.unbind = undefined;
64
+ }
65
+ }]);
66
+ }();
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.SnapshotScheduler = 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
+ /**
12
+ * Snapshots are taken 10 s, 30 s and 60 s after the session starts, then every 60 s. The
13
+ * first three come close together so that short sessions, which are the common case, are
14
+ * still reported.
15
+ */
16
+ var INITIAL_OFFSETS_MS = [10000, 30000, 60000];
17
+ var INTERVAL_MS = 60000;
18
+
19
+ /**
20
+ * Fires `onTick` on the snapshot cadence. Timings are offsets from `start()`, so a
21
+ * restart (a new session in the same editor) restarts the cadence too.
22
+ */
23
+ var SnapshotScheduler = exports.SnapshotScheduler = /*#__PURE__*/function () {
24
+ function SnapshotScheduler(onTick) {
25
+ (0, _classCallCheck2.default)(this, SnapshotScheduler);
26
+ (0, _defineProperty2.default)(this, "tickIndex", 0);
27
+ this.onTick = onTick;
28
+ }
29
+ return (0, _createClass2.default)(SnapshotScheduler, [{
30
+ key: "start",
31
+ value: function start() {
32
+ this.tickIndex = 0;
33
+ this.scheduleNext();
34
+ }
35
+ }, {
36
+ key: "restart",
37
+ value: function restart() {
38
+ this.stop();
39
+ this.start();
40
+ }
41
+ }, {
42
+ key: "stop",
43
+ value: function stop() {
44
+ if (this.timeoutId !== undefined) {
45
+ window.clearTimeout(this.timeoutId);
46
+ this.timeoutId = undefined;
47
+ }
48
+ }
49
+ }, {
50
+ key: "scheduleNext",
51
+ value: function scheduleNext() {
52
+ var _this = this;
53
+ this.timeoutId = window.setTimeout(function () {
54
+ _this.tickIndex += 1;
55
+ _this.onTick();
56
+ _this.scheduleNext();
57
+ }, this.delayForNextTick());
58
+ }
59
+ }, {
60
+ key: "delayForNextTick",
61
+ value: function delayForNextTick() {
62
+ var offset = INITIAL_OFFSETS_MS[this.tickIndex];
63
+ if (offset === undefined) {
64
+ return INTERVAL_MS;
65
+ }
66
+ var previousOffset = this.tickIndex === 0 ? 0 : INITIAL_OFFSETS_MS[this.tickIndex - 1];
67
+ return offset - previousOffset;
68
+ }
69
+ }]);
70
+ }();