@atlaskit/collab-provider 23.0.4 → 23.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,52 @@
1
1
  # @atlaskit/collab-provider
2
2
 
3
+ ## 23.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`94d2f2443bb25`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/94d2f2443bb25) -
8
+ Share the process suspension detector so consumers outside the provider can react to a device
9
+ waking, via a new `./sleep-detector` entry point.
10
+
11
+ `SleepDetector` was an internal class holding its own interval and gap history. It is replaced by
12
+ a module-scoped detector with a single shared interval:
13
+
14
+ ```ts
15
+ import {
16
+ acquireSleepDetector,
17
+ getMaxGapSince,
18
+ onSuspension,
19
+ } from '@atlaskit/collab-provider/sleep-detector';
20
+
21
+ const release = acquireSleepDetector(); // ref-counted; the interval runs until every consumer releases
22
+ const unsubscribe = onSuspension((gapMs) => {
23
+ /* the process was frozen for gapMs */
24
+ });
25
+ const sleptFor = getMaxGapSince(watermark);
26
+ ```
27
+
28
+ Consumers pass their own watermark to `getMaxGapSince` instead of clearing history, so one
29
+ consumer observing a suspension cannot hide it from another. `Provider` behaviour is unchanged; it
30
+ now holds a watermark internally where it previously reset the detector. No existing entry point
31
+ changes, so no migration is required for current consumers.
32
+
33
+ The refactor itself is not gated, but the behaviour it exists to serve is: `Provider` only acts on
34
+ a detected suspension when the `collab_check_sleep_detection_experiment` experiment is enabled,
35
+ and the first consumer of the new entry point (Confluence quick reload) is gated on the same
36
+ experiment.
37
+
38
+ ### Patch Changes
39
+
40
+ - [`b22ad50198f6a`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/b22ad50198f6a) -
41
+ Propagate the actingUserId for presence attribution
42
+ - Updated dependencies
43
+
44
+ ## 23.0.5
45
+
46
+ ### Patch Changes
47
+
48
+ - Updated dependencies
49
+
3
50
  ## 23.0.4
4
51
 
5
52
  ### Patch Changes
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ Object.defineProperty(exports, "acquireSleepDetector", {
7
+ enumerable: true,
8
+ get: function get() {
9
+ return _sleepDetector.acquireSleepDetector;
10
+ }
11
+ });
12
+ Object.defineProperty(exports, "getMaxGapSince", {
13
+ enumerable: true,
14
+ get: function get() {
15
+ return _sleepDetector.getMaxGapSince;
16
+ }
17
+ });
18
+ Object.defineProperty(exports, "onSuspension", {
19
+ enumerable: true,
20
+ get: function get() {
21
+ return _sleepDetector.onSuspension;
22
+ }
23
+ });
24
+ var _sleepDetector = require("../provider/sleep-detector");
@@ -15,11 +15,11 @@ var PARTICIPANT_UPDATE_INTERVAL = exports.PARTICIPANT_UPDATE_INTERVAL = 300 * 10
15
15
  var DEFAULT_BATCH_FETCH_SIZE = 25;
16
16
  var createParticipantFromPayload = exports.createParticipantFromPayload = /*#__PURE__*/function () {
17
17
  var _ref = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(payload, getUser) {
18
- var sessionId, timestamp, clientId, userId, permit, presenceId, presenceActivity, user, participant;
18
+ var actingUserId, sessionId, timestamp, clientId, userId, permit, presenceId, presenceActivity, user, participant;
19
19
  return _regenerator.default.wrap(function (_context) {
20
20
  while (1) switch (_context.prev = _context.next) {
21
21
  case 0:
22
- sessionId = payload.sessionId, timestamp = payload.timestamp, clientId = payload.clientId, userId = payload.userId, permit = payload.permit, presenceId = payload.presenceId, presenceActivity = payload.presenceActivity;
22
+ actingUserId = payload.actingUserId, sessionId = payload.sessionId, timestamp = payload.timestamp, clientId = payload.clientId, userId = payload.userId, permit = payload.permit, presenceId = payload.presenceId, presenceActivity = payload.presenceActivity;
23
23
  _context.next = 1;
24
24
  return getUser === null || getUser === void 0 ? void 0 : getUser(userId);
25
25
  case 1:
@@ -33,6 +33,7 @@ var createParticipantFromPayload = exports.createParticipantFromPayload = /*#__P
33
33
  userId: userId,
34
34
  clientId: clientId,
35
35
  permit: permit,
36
+ actingUserId: actingUserId,
36
37
  isGuest: user === null || user === void 0 ? void 0 : user.isGuest,
37
38
  presenceId: presenceId,
38
39
  presenceActivity: presenceActivity,
@@ -104,6 +105,7 @@ var fetchParticipants = exports.fetchParticipants = /*#__PURE__*/function () {
104
105
  lastActive: participant.lastActive,
105
106
  clientId: participant.clientId,
106
107
  permit: participant.permit,
108
+ actingUserId: participant.actingUserId,
107
109
  presenceId: participant.presenceId,
108
110
  presenceActivity: participant.presenceActivity,
109
111
  isHydrated: true
@@ -201,6 +201,9 @@ var ParticipantsService = exports.ParticipantsService = /*#__PURE__*/function ()
201
201
  (0, _defineProperty2.default)(this, "hasPresenceActivityChanged", function (previous, current) {
202
202
  return previous.presenceActivity !== current.presenceActivity;
203
203
  });
204
+ (0, _defineProperty2.default)(this, "hasActingUserChanged", function (previous, current) {
205
+ return previous.actingUserId !== current.actingUserId;
206
+ });
204
207
  (0, _defineProperty2.default)(this, "handleAnonymousUser", /*#__PURE__*/function () {
205
208
  var _ref = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(payload) {
206
209
  var _previousParticipant$, _ref2, _previousParticipant$2, _asset2;
@@ -300,6 +303,11 @@ var ParticipantsService = exports.ParticipantsService = /*#__PURE__*/function ()
300
303
  _context2.next = 7;
301
304
  break;
302
305
  }
306
+ if (_this.hasActingUserChanged(previousParticipant, participant)) {
307
+ _this.emitPresence({
308
+ joined: [participant]
309
+ }, 'handling participant acting user changed event');
310
+ }
303
311
  if (_this.hasPresenceActivityChanged(previousParticipant, participant)) {
304
312
  _this.emitPresenceActivityChange({
305
313
  type: 'participant:activity',
@@ -391,11 +399,17 @@ var ParticipantsService = exports.ParticipantsService = /*#__PURE__*/function ()
391
399
  case 4:
392
400
  // would handle activity and lastActive changes
393
401
  participant = _objectSpread(_objectSpread({}, previousParticipant), {}, {
402
+ actingUserId: payload.actingUserId,
394
403
  presenceActivity: payload.presenceActivity,
395
404
  lastActive: payload.timestamp
396
405
  });
406
+ _this.participantsState.setBySessionId(sessionId, participant);
407
+ if (_this.hasActingUserChanged(previousParticipant, participant)) {
408
+ _this.emitPresence({
409
+ joined: [participant]
410
+ }, 'handling participant acting user changed event');
411
+ }
397
412
  if (_this.hasPresenceActivityChanged(previousParticipant, participant)) {
398
- _this.participantsState.setBySessionId(sessionId, participant);
399
413
  _this.emitPresenceActivityChange({
400
414
  type: 'participant:activity',
401
415
  activity: participant.presenceActivity
@@ -68,7 +68,7 @@ var Provider = exports.Provider = /*#__PURE__*/function (_Emitter) {
68
68
  // It determines if the provider should initialize immediately and will only be true if:
69
69
  // the feature flag is enabled and the initial draft fetched from NCS is also passed in the config.
70
70
  (0, _defineProperty2.default)(_this, "isBufferingEnabled", false);
71
- (0, _defineProperty2.default)(_this, "sleepDetector", new _sleepDetector.SleepDetector());
71
+ (0, _defineProperty2.default)(_this, "sleepWatermark", 0);
72
72
  // Local IDs of active AI Providers
73
73
  (0, _defineProperty2.default)(_this, "aiProviderActiveIds", []);
74
74
  /**
@@ -180,7 +180,7 @@ var Provider = exports.Provider = /*#__PURE__*/function (_Emitter) {
180
180
  _this.participantsService.initializeFirstBatchFetchUsers();
181
181
  }
182
182
  _this.disconnectedAt = undefined;
183
- _this.sleepDetector.reset();
183
+ _this.sleepWatermark = Date.now();
184
184
  }).on('init', function (_ref3) {
185
185
  var doc = _ref3.doc,
186
186
  version = _ref3.version,
@@ -276,7 +276,7 @@ var Provider = exports.Provider = /*#__PURE__*/function (_Emitter) {
276
276
  if (disconnectedDuration >= OUT_OF_SYNC_PERIOD) {
277
277
  return disconnectedDuration;
278
278
  }
279
- var sleepDuration = _this.sleepDetector.getSleepDuration();
279
+ var sleepDuration = (0, _sleepDetector.getMaxGapSince)(_this.sleepWatermark);
280
280
  if (sleepDuration >= OUT_OF_SYNC_PERIOD && (0, _isExperimentEnabled.isExperimentEnabled)('collab_check_sleep_detection_experiment')) {
281
281
  return sleepDuration;
282
282
  }
@@ -469,7 +469,8 @@ var Provider = exports.Provider = /*#__PURE__*/function (_Emitter) {
469
469
  logger('Intervally sendStepsFromCurrentState');
470
470
  _this.documentService.sendStepsFromCurrentState(true, undefined);
471
471
  }, 5000);
472
- _this.sleepDetector.start();
472
+ _this.sleepWatermark = Date.now();
473
+ _this.releaseSleepDetector = (0, _sleepDetector.acquireSleepDetector)();
473
474
  return _this;
474
475
  }
475
476
  (0, _inherits2.default)(Provider, _Emitter);
@@ -724,13 +725,15 @@ var Provider = exports.Provider = /*#__PURE__*/function (_Emitter) {
724
725
  key: "unsubscribeAll",
725
726
  value: function unsubscribeAll() {
726
727
  try {
728
+ var _this$releaseSleepDet;
727
729
  _superPropGet(Provider, "unsubscribeAll", this, 3)([]);
728
730
  this.channel.disconnect();
729
731
  if (this.sendStepsTimer) {
730
732
  clearInterval(this.sendStepsTimer);
731
733
  this.sendStepsTimer = undefined;
732
734
  }
733
- this.sleepDetector.stop();
735
+ (_this$releaseSleepDet = this.releaseSleepDetector) === null || _this$releaseSleepDet === void 0 || _this$releaseSleepDet.call(this);
736
+ this.releaseSleepDetector = undefined;
734
737
  } catch (error) {
735
738
  var _this$analyticsHelper10;
736
739
  (_this$analyticsHelper10 = this.analyticsHelper) === null || _this$analyticsHelper10 === void 0 || _this$analyticsHelper10.sendErrorEvent(error, 'Error while shutting down the collab provider');
@@ -1,58 +1,67 @@
1
1
  "use strict";
2
2
 
3
- var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
3
  Object.defineProperty(exports, "__esModule", {
5
4
  value: true
6
5
  });
7
- exports.SleepDetector = void 0;
8
- var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
9
- var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
10
- var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
11
- var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
6
+ exports.onSuspension = exports.getMaxGapSince = exports.acquireSleepDetector = void 0;
12
7
  var TICK_INTERVAL = 1000;
13
8
  var GAP_HISTORY_SIZE = 20;
14
- var SleepDetector = exports.SleepDetector = /*#__PURE__*/function () {
15
- function SleepDetector() {
16
- var _this = this;
17
- (0, _classCallCheck2.default)(this, SleepDetector);
18
- (0, _defineProperty2.default)(this, "lastTickAt", Date.now());
19
- (0, _defineProperty2.default)(this, "gaps", []);
20
- (0, _defineProperty2.default)(this, "tick", function () {
21
- var now = Date.now();
22
- _this.gaps.push(now - _this.lastTickAt);
23
- if (_this.gaps.length > GAP_HISTORY_SIZE) {
24
- _this.gaps.shift();
25
- }
26
- _this.lastTickAt = now;
9
+ var MIN_REPORTED_GAP = TICK_INTERVAL * 2;
10
+ var lastTickAt = Date.now();
11
+ var gaps = [];
12
+ var sentinel;
13
+ var consumers = 0;
14
+ var listeners = new Set();
15
+ var tick = function tick() {
16
+ var now = Date.now();
17
+ var gap = now - lastTickAt;
18
+ lastTickAt = now;
19
+ gaps.push({
20
+ at: now,
21
+ gap: gap
22
+ });
23
+ if (gaps.length > GAP_HISTORY_SIZE) {
24
+ gaps.shift();
25
+ }
26
+ if (gap >= MIN_REPORTED_GAP) {
27
+ listeners.forEach(function (listener) {
28
+ return listener(gap);
27
29
  });
28
30
  }
29
- return (0, _createClass2.default)(SleepDetector, [{
30
- key: "start",
31
- value: function start() {
32
- if (this.sentinel) {
33
- return;
34
- }
35
- this.reset();
36
- this.sentinel = setInterval(this.tick, TICK_INTERVAL);
31
+ };
32
+ var acquireSleepDetector = exports.acquireSleepDetector = function acquireSleepDetector() {
33
+ consumers += 1;
34
+ if (!sentinel) {
35
+ gaps = [];
36
+ lastTickAt = Date.now();
37
+ sentinel = setInterval(tick, TICK_INTERVAL);
38
+ }
39
+ var released = false;
40
+ return function () {
41
+ if (released) {
42
+ return;
37
43
  }
38
- }, {
39
- key: "stop",
40
- value: function stop() {
41
- if (this.sentinel) {
42
- clearInterval(this.sentinel);
43
- this.sentinel = undefined;
44
+ released = true;
45
+ consumers -= 1;
46
+ if (consumers <= 0) {
47
+ consumers = 0;
48
+ if (sentinel) {
49
+ clearInterval(sentinel);
50
+ sentinel = undefined;
44
51
  }
45
52
  }
46
- }, {
47
- key: "reset",
48
- value: function reset() {
49
- this.gaps = [];
50
- this.lastTickAt = Date.now();
51
- }
52
- }, {
53
- key: "getSleepDuration",
54
- value: function getSleepDuration() {
55
- return this.gaps.length ? Math.max.apply(Math, (0, _toConsumableArray2.default)(this.gaps)) : 0;
56
- }
57
- }]);
58
- }();
53
+ };
54
+ };
55
+ var onSuspension = exports.onSuspension = function onSuspension(listener) {
56
+ listeners.add(listener);
57
+ return function () {
58
+ listeners.delete(listener);
59
+ };
60
+ };
61
+ var getMaxGapSince = exports.getMaxGapSince = function getMaxGapSince(since) {
62
+ return gaps.reduce(function (max, _ref) {
63
+ var at = _ref.at,
64
+ gap = _ref.gap;
65
+ return at > since && gap > max ? gap : max;
66
+ }, 0);
67
+ };
@@ -5,7 +5,7 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.version = exports.nextMajorVersion = exports.name = void 0;
7
7
  var name = exports.name = "@atlaskit/collab-provider";
8
- var version = exports.version = "23.0.3";
8
+ var version = exports.version = "23.0.5";
9
9
  var nextMajorVersion = exports.nextMajorVersion = function nextMajorVersion() {
10
10
  return [Number(version.split('.')[0]) + 1, 0, 0].join('.');
11
11
  };
@@ -0,0 +1,2 @@
1
+ /* eslint-disable @atlaskit/editor/no-re-export */
2
+ export { acquireSleepDetector, getMaxGapSince, onSuspension } from '../provider/sleep-detector';
@@ -5,6 +5,7 @@ export const PARTICIPANT_UPDATE_INTERVAL = 300 * 1000; // 300 seconds
5
5
  const DEFAULT_BATCH_FETCH_SIZE = 25;
6
6
  export const createParticipantFromPayload = async (payload, getUser) => {
7
7
  const {
8
+ actingUserId,
8
9
  sessionId,
9
10
  timestamp,
10
11
  clientId,
@@ -23,6 +24,7 @@ export const createParticipantFromPayload = async (payload, getUser) => {
23
24
  userId,
24
25
  clientId,
25
26
  permit,
27
+ actingUserId,
26
28
  isGuest: user === null || user === void 0 ? void 0 : user.isGuest,
27
29
  presenceId: presenceId,
28
30
  presenceActivity: presenceActivity,
@@ -77,6 +79,7 @@ export const fetchParticipants = async (participantsState, batchProps) => {
77
79
  lastActive: participant.lastActive,
78
80
  clientId: participant.clientId,
79
81
  permit: participant.permit,
82
+ actingUserId: participant.actingUserId,
80
83
  presenceId: participant.presenceId,
81
84
  presenceActivity: participant.presenceActivity,
82
85
  isHydrated: true
@@ -185,6 +185,9 @@ export class ParticipantsService {
185
185
  _defineProperty(this, "hasPresenceActivityChanged", (previous, current) => {
186
186
  return previous.presenceActivity !== current.presenceActivity;
187
187
  });
188
+ _defineProperty(this, "hasActingUserChanged", (previous, current) => {
189
+ return previous.actingUserId !== current.actingUserId;
190
+ });
188
191
  _defineProperty(this, "handleAnonymousUser", async payload => {
189
192
  var _previousParticipant$, _ref, _previousParticipant$2, _asset2;
190
193
  const {
@@ -250,6 +253,11 @@ export class ParticipantsService {
250
253
  const previousParticipant = this.participantsState.getBySessionId(participant.sessionId);
251
254
  this.participantsState.setBySessionId(participant.sessionId, participant);
252
255
  if (previousParticipant) {
256
+ if (this.hasActingUserChanged(previousParticipant, participant)) {
257
+ this.emitPresence({
258
+ joined: [participant]
259
+ }, 'handling participant acting user changed event');
260
+ }
253
261
  if (this.hasPresenceActivityChanged(previousParticipant, participant)) {
254
262
  this.emitPresenceActivityChange({
255
263
  type: 'participant:activity',
@@ -327,11 +335,17 @@ export class ParticipantsService {
327
335
  // would handle activity and lastActive changes
328
336
  const participant = {
329
337
  ...previousParticipant,
338
+ actingUserId: payload.actingUserId,
330
339
  presenceActivity: payload.presenceActivity,
331
340
  lastActive: payload.timestamp
332
341
  };
342
+ this.participantsState.setBySessionId(sessionId, participant);
343
+ if (this.hasActingUserChanged(previousParticipant, participant)) {
344
+ this.emitPresence({
345
+ joined: [participant]
346
+ }, 'handling participant acting user changed event');
347
+ }
333
348
  if (this.hasPresenceActivityChanged(previousParticipant, participant)) {
334
- this.participantsState.setBySessionId(sessionId, participant);
335
349
  this.emitPresenceActivityChange({
336
350
  type: 'participant:activity',
337
351
  activity: participant.presenceActivity
@@ -23,7 +23,7 @@ import { Api } from '../api/api';
23
23
  import { shouldTelepointerBeSampled } from '../analytics/performance';
24
24
  import { NullApi } from '../api/null-api';
25
25
  import { getOfflineStepsLength, getOfflineReplaceStepsLength } from './get-offline-steps-length';
26
- import { SleepDetector } from './sleep-detector';
26
+ import { acquireSleepDetector, getMaxGapSince } from './sleep-detector';
27
27
  const logger = createLogger('Provider', 'black');
28
28
  const OUT_OF_SYNC_PERIOD = 3 * 1000; // 3 seconds
29
29
 
@@ -47,7 +47,7 @@ export class Provider extends Emitter {
47
47
  // It determines if the provider should initialize immediately and will only be true if:
48
48
  // the feature flag is enabled and the initial draft fetched from NCS is also passed in the config.
49
49
  _defineProperty(this, "isBufferingEnabled", false);
50
- _defineProperty(this, "sleepDetector", new SleepDetector());
50
+ _defineProperty(this, "sleepWatermark", 0);
51
51
  // Local IDs of active AI Providers
52
52
  _defineProperty(this, "aiProviderActiveIds", []);
53
53
  /**
@@ -160,7 +160,7 @@ export class Provider extends Emitter {
160
160
  this.participantsService.initializeFirstBatchFetchUsers();
161
161
  }
162
162
  this.disconnectedAt = undefined;
163
- this.sleepDetector.reset();
163
+ this.sleepWatermark = Date.now();
164
164
  }).on('init', ({
165
165
  doc,
166
166
  version,
@@ -239,7 +239,7 @@ export class Provider extends Emitter {
239
239
  if (disconnectedDuration >= OUT_OF_SYNC_PERIOD) {
240
240
  return disconnectedDuration;
241
241
  }
242
- const sleepDuration = this.sleepDetector.getSleepDuration();
242
+ const sleepDuration = getMaxGapSince(this.sleepWatermark);
243
243
  if (sleepDuration >= OUT_OF_SYNC_PERIOD && isExperimentEnabled('collab_check_sleep_detection_experiment')) {
244
244
  return sleepDuration;
245
245
  }
@@ -384,7 +384,8 @@ export class Provider extends Emitter {
384
384
  logger('Intervally sendStepsFromCurrentState');
385
385
  this.documentService.sendStepsFromCurrentState(true, undefined);
386
386
  }, 5000);
387
- this.sleepDetector.start();
387
+ this.sleepWatermark = Date.now();
388
+ this.releaseSleepDetector = acquireSleepDetector();
388
389
  }
389
390
  // eslint-disable-next-line @repo/internal/deprecations/deprecation-ticket-required -- Ignored via go/ED-25883
390
391
  /**
@@ -620,13 +621,15 @@ export class Provider extends Emitter {
620
621
  */
621
622
  unsubscribeAll() {
622
623
  try {
624
+ var _this$releaseSleepDet;
623
625
  super.unsubscribeAll();
624
626
  this.channel.disconnect();
625
627
  if (this.sendStepsTimer) {
626
628
  clearInterval(this.sendStepsTimer);
627
629
  this.sendStepsTimer = undefined;
628
630
  }
629
- this.sleepDetector.stop();
631
+ (_this$releaseSleepDet = this.releaseSleepDetector) === null || _this$releaseSleepDet === void 0 ? void 0 : _this$releaseSleepDet.call(this);
632
+ this.releaseSleepDetector = undefined;
630
633
  } catch (error) {
631
634
  var _this$analyticsHelper12;
632
635
  (_this$analyticsHelper12 = this.analyticsHelper) === null || _this$analyticsHelper12 === void 0 ? void 0 : _this$analyticsHelper12.sendErrorEvent(error, 'Error while shutting down the collab provider');
@@ -1,37 +1,56 @@
1
- import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
1
  const TICK_INTERVAL = 1000;
3
2
  const GAP_HISTORY_SIZE = 20;
4
- export class SleepDetector {
5
- constructor() {
6
- _defineProperty(this, "lastTickAt", Date.now());
7
- _defineProperty(this, "gaps", []);
8
- _defineProperty(this, "tick", () => {
9
- const now = Date.now();
10
- this.gaps.push(now - this.lastTickAt);
11
- if (this.gaps.length > GAP_HISTORY_SIZE) {
12
- this.gaps.shift();
13
- }
14
- this.lastTickAt = now;
15
- });
3
+ const MIN_REPORTED_GAP = TICK_INTERVAL * 2;
4
+ let lastTickAt = Date.now();
5
+ let gaps = [];
6
+ let sentinel;
7
+ let consumers = 0;
8
+ const listeners = new Set();
9
+ const tick = () => {
10
+ const now = Date.now();
11
+ const gap = now - lastTickAt;
12
+ lastTickAt = now;
13
+ gaps.push({
14
+ at: now,
15
+ gap
16
+ });
17
+ if (gaps.length > GAP_HISTORY_SIZE) {
18
+ gaps.shift();
19
+ }
20
+ if (gap >= MIN_REPORTED_GAP) {
21
+ listeners.forEach(listener => listener(gap));
16
22
  }
17
- start() {
18
- if (this.sentinel) {
23
+ };
24
+ export const acquireSleepDetector = () => {
25
+ consumers += 1;
26
+ if (!sentinel) {
27
+ gaps = [];
28
+ lastTickAt = Date.now();
29
+ sentinel = setInterval(tick, TICK_INTERVAL);
30
+ }
31
+ let released = false;
32
+ return () => {
33
+ if (released) {
19
34
  return;
20
35
  }
21
- this.reset();
22
- this.sentinel = setInterval(this.tick, TICK_INTERVAL);
23
- }
24
- stop() {
25
- if (this.sentinel) {
26
- clearInterval(this.sentinel);
27
- this.sentinel = undefined;
36
+ released = true;
37
+ consumers -= 1;
38
+ if (consumers <= 0) {
39
+ consumers = 0;
40
+ if (sentinel) {
41
+ clearInterval(sentinel);
42
+ sentinel = undefined;
43
+ }
28
44
  }
29
- }
30
- reset() {
31
- this.gaps = [];
32
- this.lastTickAt = Date.now();
33
- }
34
- getSleepDuration() {
35
- return this.gaps.length ? Math.max(...this.gaps) : 0;
36
- }
37
- }
45
+ };
46
+ };
47
+ export const onSuspension = listener => {
48
+ listeners.add(listener);
49
+ return () => {
50
+ listeners.delete(listener);
51
+ };
52
+ };
53
+ export const getMaxGapSince = since => gaps.reduce((max, {
54
+ at,
55
+ gap
56
+ }) => at > since && gap > max ? gap : max, 0);
@@ -1,5 +1,5 @@
1
1
  export const name = "@atlaskit/collab-provider";
2
- export const version = "23.0.3";
2
+ export const version = "23.0.5";
3
3
  export const nextMajorVersion = () => {
4
4
  return [Number(version.split('.')[0]) + 1, 0, 0].join('.');
5
5
  };
@@ -0,0 +1,2 @@
1
+ /* eslint-disable @atlaskit/editor/no-re-export */
2
+ export { acquireSleepDetector, getMaxGapSince, onSuspension } from '../provider/sleep-detector';
@@ -8,11 +8,11 @@ export var PARTICIPANT_UPDATE_INTERVAL = 300 * 1000; // 300 seconds
8
8
  var DEFAULT_BATCH_FETCH_SIZE = 25;
9
9
  export var createParticipantFromPayload = /*#__PURE__*/function () {
10
10
  var _ref = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(payload, getUser) {
11
- var sessionId, timestamp, clientId, userId, permit, presenceId, presenceActivity, user, participant;
11
+ var actingUserId, sessionId, timestamp, clientId, userId, permit, presenceId, presenceActivity, user, participant;
12
12
  return _regeneratorRuntime.wrap(function (_context) {
13
13
  while (1) switch (_context.prev = _context.next) {
14
14
  case 0:
15
- sessionId = payload.sessionId, timestamp = payload.timestamp, clientId = payload.clientId, userId = payload.userId, permit = payload.permit, presenceId = payload.presenceId, presenceActivity = payload.presenceActivity;
15
+ actingUserId = payload.actingUserId, sessionId = payload.sessionId, timestamp = payload.timestamp, clientId = payload.clientId, userId = payload.userId, permit = payload.permit, presenceId = payload.presenceId, presenceActivity = payload.presenceActivity;
16
16
  _context.next = 1;
17
17
  return getUser === null || getUser === void 0 ? void 0 : getUser(userId);
18
18
  case 1:
@@ -26,6 +26,7 @@ export var createParticipantFromPayload = /*#__PURE__*/function () {
26
26
  userId: userId,
27
27
  clientId: clientId,
28
28
  permit: permit,
29
+ actingUserId: actingUserId,
29
30
  isGuest: user === null || user === void 0 ? void 0 : user.isGuest,
30
31
  presenceId: presenceId,
31
32
  presenceActivity: presenceActivity,
@@ -97,6 +98,7 @@ export var fetchParticipants = /*#__PURE__*/function () {
97
98
  lastActive: participant.lastActive,
98
99
  clientId: participant.clientId,
99
100
  permit: participant.permit,
101
+ actingUserId: participant.actingUserId,
100
102
  presenceId: participant.presenceId,
101
103
  presenceActivity: participant.presenceActivity,
102
104
  isHydrated: true
@@ -194,6 +194,9 @@ export var ParticipantsService = /*#__PURE__*/function () {
194
194
  _defineProperty(this, "hasPresenceActivityChanged", function (previous, current) {
195
195
  return previous.presenceActivity !== current.presenceActivity;
196
196
  });
197
+ _defineProperty(this, "hasActingUserChanged", function (previous, current) {
198
+ return previous.actingUserId !== current.actingUserId;
199
+ });
197
200
  _defineProperty(this, "handleAnonymousUser", /*#__PURE__*/function () {
198
201
  var _ref = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(payload) {
199
202
  var _previousParticipant$, _ref2, _previousParticipant$2, _asset2;
@@ -293,6 +296,11 @@ export var ParticipantsService = /*#__PURE__*/function () {
293
296
  _context2.next = 7;
294
297
  break;
295
298
  }
299
+ if (_this.hasActingUserChanged(previousParticipant, participant)) {
300
+ _this.emitPresence({
301
+ joined: [participant]
302
+ }, 'handling participant acting user changed event');
303
+ }
296
304
  if (_this.hasPresenceActivityChanged(previousParticipant, participant)) {
297
305
  _this.emitPresenceActivityChange({
298
306
  type: 'participant:activity',
@@ -384,11 +392,17 @@ export var ParticipantsService = /*#__PURE__*/function () {
384
392
  case 4:
385
393
  // would handle activity and lastActive changes
386
394
  participant = _objectSpread(_objectSpread({}, previousParticipant), {}, {
395
+ actingUserId: payload.actingUserId,
387
396
  presenceActivity: payload.presenceActivity,
388
397
  lastActive: payload.timestamp
389
398
  });
399
+ _this.participantsState.setBySessionId(sessionId, participant);
400
+ if (_this.hasActingUserChanged(previousParticipant, participant)) {
401
+ _this.emitPresence({
402
+ joined: [participant]
403
+ }, 'handling participant acting user changed event');
404
+ }
390
405
  if (_this.hasPresenceActivityChanged(previousParticipant, participant)) {
391
- _this.participantsState.setBySessionId(sessionId, participant);
392
406
  _this.emitPresenceActivityChange({
393
407
  type: 'participant:activity',
394
408
  activity: participant.presenceActivity
@@ -36,7 +36,7 @@ import { Api } from '../api/api';
36
36
  import { shouldTelepointerBeSampled } from '../analytics/performance';
37
37
  import { NullApi } from '../api/null-api';
38
38
  import { getOfflineStepsLength, getOfflineReplaceStepsLength } from './get-offline-steps-length';
39
- import { SleepDetector } from './sleep-detector';
39
+ import { acquireSleepDetector, getMaxGapSince } from './sleep-detector';
40
40
  var logger = createLogger('Provider', 'black');
41
41
  var OUT_OF_SYNC_PERIOD = 3 * 1000; // 3 seconds
42
42
 
@@ -62,7 +62,7 @@ export var Provider = /*#__PURE__*/function (_Emitter) {
62
62
  // It determines if the provider should initialize immediately and will only be true if:
63
63
  // the feature flag is enabled and the initial draft fetched from NCS is also passed in the config.
64
64
  _defineProperty(_this, "isBufferingEnabled", false);
65
- _defineProperty(_this, "sleepDetector", new SleepDetector());
65
+ _defineProperty(_this, "sleepWatermark", 0);
66
66
  // Local IDs of active AI Providers
67
67
  _defineProperty(_this, "aiProviderActiveIds", []);
68
68
  /**
@@ -174,7 +174,7 @@ export var Provider = /*#__PURE__*/function (_Emitter) {
174
174
  _this.participantsService.initializeFirstBatchFetchUsers();
175
175
  }
176
176
  _this.disconnectedAt = undefined;
177
- _this.sleepDetector.reset();
177
+ _this.sleepWatermark = Date.now();
178
178
  }).on('init', function (_ref3) {
179
179
  var doc = _ref3.doc,
180
180
  version = _ref3.version,
@@ -270,7 +270,7 @@ export var Provider = /*#__PURE__*/function (_Emitter) {
270
270
  if (disconnectedDuration >= OUT_OF_SYNC_PERIOD) {
271
271
  return disconnectedDuration;
272
272
  }
273
- var sleepDuration = _this.sleepDetector.getSleepDuration();
273
+ var sleepDuration = getMaxGapSince(_this.sleepWatermark);
274
274
  if (sleepDuration >= OUT_OF_SYNC_PERIOD && isExperimentEnabled('collab_check_sleep_detection_experiment')) {
275
275
  return sleepDuration;
276
276
  }
@@ -463,7 +463,8 @@ export var Provider = /*#__PURE__*/function (_Emitter) {
463
463
  logger('Intervally sendStepsFromCurrentState');
464
464
  _this.documentService.sendStepsFromCurrentState(true, undefined);
465
465
  }, 5000);
466
- _this.sleepDetector.start();
466
+ _this.sleepWatermark = Date.now();
467
+ _this.releaseSleepDetector = acquireSleepDetector();
467
468
  return _this;
468
469
  }
469
470
  _inherits(Provider, _Emitter);
@@ -718,13 +719,15 @@ export var Provider = /*#__PURE__*/function (_Emitter) {
718
719
  key: "unsubscribeAll",
719
720
  value: function unsubscribeAll() {
720
721
  try {
722
+ var _this$releaseSleepDet;
721
723
  _superPropGet(Provider, "unsubscribeAll", this, 3)([]);
722
724
  this.channel.disconnect();
723
725
  if (this.sendStepsTimer) {
724
726
  clearInterval(this.sendStepsTimer);
725
727
  this.sendStepsTimer = undefined;
726
728
  }
727
- this.sleepDetector.stop();
729
+ (_this$releaseSleepDet = this.releaseSleepDetector) === null || _this$releaseSleepDet === void 0 || _this$releaseSleepDet.call(this);
730
+ this.releaseSleepDetector = undefined;
728
731
  } catch (error) {
729
732
  var _this$analyticsHelper10;
730
733
  (_this$analyticsHelper10 = this.analyticsHelper) === null || _this$analyticsHelper10 === void 0 || _this$analyticsHelper10.sendErrorEvent(error, 'Error while shutting down the collab provider');
@@ -1,51 +1,61 @@
1
- import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
2
- import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
3
- import _createClass from "@babel/runtime/helpers/createClass";
4
- import _defineProperty from "@babel/runtime/helpers/defineProperty";
5
1
  var TICK_INTERVAL = 1000;
6
2
  var GAP_HISTORY_SIZE = 20;
7
- export var SleepDetector = /*#__PURE__*/function () {
8
- function SleepDetector() {
9
- var _this = this;
10
- _classCallCheck(this, SleepDetector);
11
- _defineProperty(this, "lastTickAt", Date.now());
12
- _defineProperty(this, "gaps", []);
13
- _defineProperty(this, "tick", function () {
14
- var now = Date.now();
15
- _this.gaps.push(now - _this.lastTickAt);
16
- if (_this.gaps.length > GAP_HISTORY_SIZE) {
17
- _this.gaps.shift();
18
- }
19
- _this.lastTickAt = now;
3
+ var MIN_REPORTED_GAP = TICK_INTERVAL * 2;
4
+ var lastTickAt = Date.now();
5
+ var gaps = [];
6
+ var sentinel;
7
+ var consumers = 0;
8
+ var listeners = new Set();
9
+ var tick = function tick() {
10
+ var now = Date.now();
11
+ var gap = now - lastTickAt;
12
+ lastTickAt = now;
13
+ gaps.push({
14
+ at: now,
15
+ gap: gap
16
+ });
17
+ if (gaps.length > GAP_HISTORY_SIZE) {
18
+ gaps.shift();
19
+ }
20
+ if (gap >= MIN_REPORTED_GAP) {
21
+ listeners.forEach(function (listener) {
22
+ return listener(gap);
20
23
  });
21
24
  }
22
- return _createClass(SleepDetector, [{
23
- key: "start",
24
- value: function start() {
25
- if (this.sentinel) {
26
- return;
27
- }
28
- this.reset();
29
- this.sentinel = setInterval(this.tick, TICK_INTERVAL);
25
+ };
26
+ export var acquireSleepDetector = function acquireSleepDetector() {
27
+ consumers += 1;
28
+ if (!sentinel) {
29
+ gaps = [];
30
+ lastTickAt = Date.now();
31
+ sentinel = setInterval(tick, TICK_INTERVAL);
32
+ }
33
+ var released = false;
34
+ return function () {
35
+ if (released) {
36
+ return;
30
37
  }
31
- }, {
32
- key: "stop",
33
- value: function stop() {
34
- if (this.sentinel) {
35
- clearInterval(this.sentinel);
36
- this.sentinel = undefined;
38
+ released = true;
39
+ consumers -= 1;
40
+ if (consumers <= 0) {
41
+ consumers = 0;
42
+ if (sentinel) {
43
+ clearInterval(sentinel);
44
+ sentinel = undefined;
37
45
  }
38
46
  }
39
- }, {
40
- key: "reset",
41
- value: function reset() {
42
- this.gaps = [];
43
- this.lastTickAt = Date.now();
44
- }
45
- }, {
46
- key: "getSleepDuration",
47
- value: function getSleepDuration() {
48
- return this.gaps.length ? Math.max.apply(Math, _toConsumableArray(this.gaps)) : 0;
49
- }
50
- }]);
51
- }();
47
+ };
48
+ };
49
+ export var onSuspension = function onSuspension(listener) {
50
+ listeners.add(listener);
51
+ return function () {
52
+ listeners.delete(listener);
53
+ };
54
+ };
55
+ export var getMaxGapSince = function getMaxGapSince(since) {
56
+ return gaps.reduce(function (max, _ref) {
57
+ var at = _ref.at,
58
+ gap = _ref.gap;
59
+ return at > since && gap > max ? gap : max;
60
+ }, 0);
61
+ };
@@ -1,5 +1,5 @@
1
1
  export var name = "@atlaskit/collab-provider";
2
- export var version = "23.0.3";
2
+ export var version = "23.0.5";
3
3
  export var nextMajorVersion = function nextMajorVersion() {
4
4
  return [Number(version.split('.')[0]) + 1, 0, 0].join('.');
5
5
  };
@@ -0,0 +1 @@
1
+ export { acquireSleepDetector, getMaxGapSince, onSuspension } from '../provider/sleep-detector';
@@ -100,6 +100,7 @@ export declare class ParticipantsService {
100
100
  */
101
101
  private clearAgentPresenceTimers;
102
102
  private hasPresenceActivityChanged;
103
+ private hasActingUserChanged;
103
104
  private handleAnonymousUser;
104
105
  /**
105
106
  * Carries out 3 things: 1) enriches the participant with user data, 2) updates the participantsState, 3) emits the presence event
@@ -35,7 +35,8 @@ export declare class Provider extends Emitter<CollabEvents> implements CollabEdi
35
35
  private presenceUpdateTimeout?;
36
36
  private disconnectedAt?;
37
37
  private sendStepsTimer?;
38
- private readonly sleepDetector;
38
+ private releaseSleepDetector?;
39
+ private sleepWatermark;
39
40
  private readonly participantsService;
40
41
  private readonly metadataService;
41
42
  private readonly documentService;
@@ -1,10 +1,5 @@
1
- export declare class SleepDetector {
2
- private lastTickAt;
3
- private gaps;
4
- private sentinel?;
5
- start(): void;
6
- stop(): void;
7
- reset(): void;
8
- getSleepDuration(): number;
9
- private tick;
10
- }
1
+ type SuspensionListener = (gapMs: number) => void;
2
+ export declare const acquireSleepDetector: () => (() => void);
3
+ export declare const onSuspension: (listener: SuspensionListener) => (() => void);
4
+ export declare const getMaxGapSince: (since: number) => number;
5
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/collab-provider",
3
- "version": "23.0.4",
3
+ "version": "23.1.0",
4
4
  "description": "A provider for collaborative editing.",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org/"
@@ -29,8 +29,8 @@
29
29
  "@atlaskit/platform-feature-experiments": "^0.3.0",
30
30
  "@atlaskit/platform-feature-flags": "^2.1.0",
31
31
  "@atlaskit/prosemirror-collab": "^1.0.0",
32
- "@atlaskit/react-ufo": "^7.3.0",
33
- "@atlaskit/tmp-editor-statsig": "^145.0.0",
32
+ "@atlaskit/react-ufo": "^7.4.0",
33
+ "@atlaskit/tmp-editor-statsig": "^147.0.0",
34
34
  "@atlaskit/ufo": "^1.0.0",
35
35
  "@atlaskit/util-service-support": "^7.2.0",
36
36
  "@babel/runtime": "^7.0.0",
@@ -70,6 +70,6 @@
70
70
  }
71
71
  },
72
72
  "peerDependencies": {
73
- "@atlaskit/editor-common": "^118.4.0"
73
+ "@atlaskit/editor-common": "^118.10.0"
74
74
  }
75
75
  }
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "@atlaskit/collab-provider/sleep-detector",
3
+ "main": "../dist/cjs/entry-points/sleep-detector.js",
4
+ "module": "../dist/esm/entry-points/sleep-detector.js",
5
+ "module:es2019": "../dist/es2019/entry-points/sleep-detector.js",
6
+ "sideEffects": false,
7
+ "types": "../dist/types/entry-points/sleep-detector.d.ts"
8
+ }