@atlaskit/collab-provider 10.14.4 → 10.16.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 (35) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/cjs/channel.js +61 -0
  3. package/dist/cjs/participants/participants-helper.js +30 -19
  4. package/dist/cjs/participants/participants-service.js +42 -9
  5. package/dist/cjs/participants/participants-state.js +7 -3
  6. package/dist/cjs/provider/commit-step.js +1 -1
  7. package/dist/cjs/provider/index.js +9 -9
  8. package/dist/cjs/version-wrapper.js +1 -1
  9. package/dist/es2019/channel.js +57 -0
  10. package/dist/es2019/participants/participants-helper.js +30 -19
  11. package/dist/es2019/participants/participants-service.js +42 -9
  12. package/dist/es2019/participants/participants-state.js +5 -3
  13. package/dist/es2019/provider/commit-step.js +1 -1
  14. package/dist/es2019/provider/index.js +6 -8
  15. package/dist/es2019/version-wrapper.js +1 -1
  16. package/dist/esm/channel.js +61 -0
  17. package/dist/esm/participants/participants-helper.js +30 -19
  18. package/dist/esm/participants/participants-service.js +42 -9
  19. package/dist/esm/participants/participants-state.js +7 -3
  20. package/dist/esm/provider/commit-step.js +1 -1
  21. package/dist/esm/provider/index.js +9 -9
  22. package/dist/esm/version-wrapper.js +1 -1
  23. package/dist/types/channel.d.ts +16 -0
  24. package/dist/types/participants/participants-helper.d.ts +12 -0
  25. package/dist/types/participants/participants-service.d.ts +40 -5
  26. package/dist/types/participants/participants-state.d.ts +2 -2
  27. package/dist/types/provider/index.d.ts +4 -7
  28. package/dist/types/types.d.ts +7 -0
  29. package/dist/types-ts4.5/channel.d.ts +16 -0
  30. package/dist/types-ts4.5/participants/participants-helper.d.ts +12 -0
  31. package/dist/types-ts4.5/participants/participants-service.d.ts +40 -5
  32. package/dist/types-ts4.5/participants/participants-state.d.ts +2 -2
  33. package/dist/types-ts4.5/provider/index.d.ts +4 -7
  34. package/dist/types-ts4.5/types.d.ts +7 -0
  35. package/package.json +5 -7
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # @atlaskit/collab-provider
2
2
 
3
+ ## 10.16.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#158254](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/pull-requests/158254)
8
+ [`9ce9448c0e11a`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/9ce9448c0e11a) -
9
+ Add auto-close for Presence connections when window is in background
10
+
11
+ ### Patch Changes
12
+
13
+ - Updated dependencies
14
+
15
+ ## 10.15.0
16
+
17
+ ### Minor Changes
18
+
19
+ - [#157909](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/pull-requests/157909)
20
+ [`384136c930190`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/384136c930190) -
21
+ Add support for initializing collab-provider with presenceActivity
22
+
3
23
  ## 10.14.4
4
24
 
5
25
  ### Patch Changes
@@ -25,6 +25,8 @@ var _network = _interopRequireDefault(require("./connectivity/network"));
25
25
  var _internalErrors = require("./errors/internal-errors");
26
26
  var _ncsErrors = require("./errors/ncs-errors");
27
27
  var _customErrors = require("./errors/custom-errors");
28
+ var _featureGateJsClient = _interopRequireDefault(require("@atlaskit/feature-gate-js-client"));
29
+ var _bindEventListener = require("bind-event-listener");
28
30
  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; } } }; }
29
31
  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; } }
30
32
  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; }
@@ -418,6 +420,40 @@ var Channel = exports.Channel = /*#__PURE__*/function (_Emitter) {
418
420
  (_this$socket3 = _this.socket) === null || _this$socket3 === void 0 || _this$socket3.connect();
419
421
  }
420
422
  });
423
+ /**
424
+ * Unbinds event listeners and timers used when handling connection auto-close when tab is hidden
425
+ */
426
+ (0, _defineProperty2.default)(_this, "cleanupAutoDisconnect", function () {
427
+ _this.unbindVisibilityListener();
428
+ if (_this.disconnectTimer) {
429
+ clearTimeout(_this.disconnectTimer);
430
+ _this.disconnectTimer = undefined;
431
+ }
432
+ });
433
+ /**
434
+ * Cleanup the visiblitychange listener upon Collab Provider destroy
435
+ * Value set when the listener is binded in addVisiblityListener
436
+ */
437
+ (0, _defineProperty2.default)(_this, "unbindVisibilityListener", function () {});
438
+ (0, _defineProperty2.default)(_this, "autoDisconnect", function (disconnectTimer, disconnectDelay) {
439
+ if (document.hidden) {
440
+ logger('visibilitychange: hidden');
441
+ return setTimeout(function () {
442
+ var _this$socket4;
443
+ logger('visibilitychange: closing connection');
444
+ (_this$socket4 = _this.socket) === null || _this$socket4 === void 0 || _this$socket4.close();
445
+ }, disconnectDelay || disconnectDelay === 0 ? disconnectDelay * 1000 : 60 * 1000);
446
+ } else {
447
+ var _this$socket5;
448
+ logger('visibilitychange: visible');
449
+ if (disconnectTimer) {
450
+ clearTimeout(disconnectTimer);
451
+ }
452
+ logger('visibilitychange: re-connecting');
453
+ (_this$socket5 = _this.socket) === null || _this$socket5 === void 0 || _this$socket5.connect();
454
+ return;
455
+ }
456
+ });
421
457
  _this.config = config;
422
458
  _this.analyticsHelper = analyticsHelper;
423
459
  _this.initExperience = (0, _ufo.createDocInitExp)(_this.analyticsHelper);
@@ -651,6 +687,10 @@ var Channel = exports.Channel = /*#__PURE__*/function (_Emitter) {
651
687
  this.reconnectHelper = new _reconnectHelper.default();
652
688
  // Fired upon a reconnection attempt error (from Socket.IO Manager)
653
689
  this.socket.io.on('reconnect_error', this.onReconnectError);
690
+
691
+ // Automatic disconnect on tab background with delay, to keep presence UX accurate
692
+ // and reduce wasted connections. Reconnects upon tab becoming active again
693
+ this.addVisiblityListener();
654
694
  }
655
695
 
656
696
  // Ignored via go/ees005
@@ -775,11 +815,32 @@ var Channel = exports.Channel = /*#__PURE__*/function (_Emitter) {
775
815
  value: function isLimitExceeded(stepLimit, stepSizeLimit, maxStepSizeLimit) {
776
816
  return stepLimit > 0 && this.stepCounter > stepLimit || stepSizeLimit > 0 && this.stepSizeCounter > stepSizeLimit || maxStepSizeLimit > 0 && this.maxStepSize > maxStepSizeLimit;
777
817
  }
818
+ }, {
819
+ key: "addVisiblityListener",
820
+ value:
821
+ /**
822
+ * Adds an event listener for visibilitychange events to handle auto-disconnection if tab is in background
823
+ */
824
+ function addVisiblityListener() {
825
+ var _FeatureGates$getExpe,
826
+ _this3 = this;
827
+ var disconnectDelay = (_FeatureGates$getExpe = _featureGateJsClient.default.getExperimentValue('platform_editor_connection_auto_disconnect_delay', 'delay', -1)) !== null && _FeatureGates$getExpe !== void 0 ? _FeatureGates$getExpe : -1;
828
+ var isAutoDisconnectEnabled = disconnectDelay >= 0;
829
+ if (isAutoDisconnectEnabled && this.config.isPresenceOnly) {
830
+ this.unbindVisibilityListener = (0, _bindEventListener.bind)(document, {
831
+ type: 'visibilitychange',
832
+ listener: function listener() {
833
+ _this3.disconnectTimer = _this3.autoDisconnect(_this3.disconnectTimer, disconnectDelay);
834
+ }
835
+ });
836
+ }
837
+ }
778
838
  }, {
779
839
  key: "disconnect",
780
840
  value: function disconnect() {
781
841
  var _this$network;
782
842
  this.unsubscribeAll();
843
+ this.cleanupAutoDisconnect();
783
844
  (_this$network = this.network) === null || _this$network === void 0 || _this$network.destroy();
784
845
  this.network = null;
785
846
  if (this.socket) {
@@ -49,13 +49,26 @@ var createParticipantFromPayload = exports.createParticipantFromPayload = /*#__P
49
49
  return _ref.apply(this, arguments);
50
50
  };
51
51
  }();
52
+
53
+ /**
54
+ * Will use the getUsers callback from batchProps to fetch users
55
+ *
56
+ * 1. Determine all the participants that need to be hydrated
57
+ * 2. Only fetch a subset of those participants based on batchSize
58
+ * 3. Of the users fetched, find all of those users' sessions and mark those entries as hydrated
59
+ *
60
+ * @param participantsState
61
+ * @param batchProps
62
+ * @returns
63
+ * @example
64
+ */
52
65
  var fetchParticipants = exports.fetchParticipants = /*#__PURE__*/function () {
53
66
  var _ref2 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee2(participantsState, batchProps) {
54
67
  var _batchProps$batchSize, batchSize, getUsers, participantsToHydrate, participants, aaids, users, hydratedParticipants;
55
68
  return _regenerator.default.wrap(function _callee2$(_context2) {
56
69
  while (1) switch (_context2.prev = _context2.next) {
57
70
  case 0:
58
- _batchProps$batchSize = batchProps.batchSize, batchSize = _batchProps$batchSize === void 0 ? batchProps.batchSize || DEFAULT_BATCH_FETCH_SIZE : _batchProps$batchSize, getUsers = batchProps.getUsers;
71
+ _batchProps$batchSize = batchProps.batchSize, batchSize = _batchProps$batchSize === void 0 ? DEFAULT_BATCH_FETCH_SIZE : _batchProps$batchSize, getUsers = batchProps.getUsers;
59
72
  participantsToHydrate = participantsState.getUniqueParticipants({
60
73
  isHydrated: false
61
74
  });
@@ -81,24 +94,22 @@ var fetchParticipants = exports.fetchParticipants = /*#__PURE__*/function () {
81
94
  return p.userId === user.userId;
82
95
  }).forEach(function (participant) {
83
96
  var sessionId = participant.sessionId;
84
- if (participant && sessionId) {
85
- var hydratedParticipant = {
86
- name: (user === null || user === void 0 ? void 0 : user.name) || '',
87
- avatar: (user === null || user === void 0 ? void 0 : user.avatar) || '',
88
- email: (user === null || user === void 0 ? void 0 : user.email) || '',
89
- sessionId: sessionId,
90
- lastActive: participant.lastActive,
91
- userId: user.userId,
92
- clientId: participant.clientId,
93
- permit: participant.permit,
94
- isGuest: user === null || user === void 0 ? void 0 : user.isGuest,
95
- presenceId: participant.presenceId,
96
- presenceActivity: participant.presenceActivity,
97
- isHydrated: true
98
- };
99
- participantsState.setBySessionId(sessionId, hydratedParticipant);
100
- hydratedParticipants.push(hydratedParticipant);
101
- }
97
+ var hydratedParticipant = {
98
+ name: user.name,
99
+ avatar: user.avatar,
100
+ email: user.email,
101
+ userId: user.userId,
102
+ isGuest: user.isGuest,
103
+ sessionId: sessionId,
104
+ lastActive: participant.lastActive,
105
+ clientId: participant.clientId,
106
+ permit: participant.permit,
107
+ presenceId: participant.presenceId,
108
+ presenceActivity: participant.presenceActivity,
109
+ isHydrated: true
110
+ };
111
+ participantsState.setBySessionId(sessionId, hydratedParticipant);
112
+ hydratedParticipants.push(hydratedParticipant);
102
113
  });
103
114
  });
104
115
  return _context2.abrupt("return", hydratedParticipants);
@@ -33,6 +33,20 @@ var UNIDENTIFIED = 'unidentified';
33
33
  * @param sendPresenceJoined Callback to Channel class
34
34
  */
35
35
  var ParticipantsService = exports.ParticipantsService = /*#__PURE__*/function () {
36
+ /**
37
+ *
38
+ * @param analyticsHelper
39
+ * @param participantsState
40
+ * @param emit
41
+ * @param getUser
42
+ * @param batchProps
43
+ * @param channelBroadcast
44
+ * @param sendPresenceJoined
45
+ * @param getPresenceData
46
+ * @param setUserId
47
+ * @param getAIProviderActiveIds
48
+ * @example
49
+ */
36
50
  function ParticipantsService(analyticsHelper) {
37
51
  var _this = this;
38
52
  var participantsState = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : new _participantsState.ParticipantsState();
@@ -100,6 +114,7 @@ var ParticipantsService = exports.ParticipantsService = /*#__PURE__*/function ()
100
114
  /**
101
115
  * Carries out 3 things: 1) enriches the participant with user data, 2) updates the participantsState, 3) emits the presence event
102
116
  * @param payload Payload from incoming socket event
117
+ * @example
103
118
  */
104
119
  (0, _defineProperty2.default)(this, "updateParticipantEager", /*#__PURE__*/function () {
105
120
  var _ref = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(payload) {
@@ -173,12 +188,15 @@ var ParticipantsService = exports.ParticipantsService = /*#__PURE__*/function ()
173
188
  * 3. If incoming participant is not new, update previous state with new values (timestamp, activity)
174
189
  *
175
190
  * @param payload Payload from incoming socket event
191
+ * @example
176
192
  */
177
193
  (0, _defineProperty2.default)(this, "updateParticipantLazy", function (payload) {
178
194
  var userId = payload.userId,
179
195
  sessionId = payload.sessionId;
180
196
 
181
- // anonymous users always skip hydration
197
+ // anonymous users always skip hydration but are marked as hydrated since we don't want to attempt to fetch data
198
+ // this can cause interesting behavior if batchProps.participantsLimit exists
199
+ // for example if limit = 5 and we've hydrated 5 real users and 2 anonymous users join, it'll look like we've hydrated 7 users
182
200
  if (!userId || userId === UNIDENTIFIED) {
183
201
  var _participant = _objectSpread(_objectSpread({}, payload), {}, {
184
202
  lastActive: payload.timestamp,
@@ -223,10 +241,6 @@ var ParticipantsService = exports.ParticipantsService = /*#__PURE__*/function ()
223
241
  _this.emitPresence({
224
242
  joined: [participant]
225
243
  }, 'handling updated previous participant event');
226
- // prevent running multiple debounces concurrently
227
- if (!_this.currentlyPollingFetchUsers) {
228
- void _this.batchFetchUsers();
229
- }
230
244
  });
231
245
  (0, _defineProperty2.default)(this, "onParticipantUpdated", /*#__PURE__*/function () {
232
246
  var _ref2 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee2(payload) {
@@ -256,6 +270,8 @@ var ParticipantsService = exports.ParticipantsService = /*#__PURE__*/function ()
256
270
  /**
257
271
  * Called when a participant leaves the session.
258
272
  * We emit the `presence` event to update the active avatars in the editor.
273
+ * @param payload
274
+ * @example
259
275
  */
260
276
  (0, _defineProperty2.default)(this, "onParticipantLeft", function (payload) {
261
277
  var _payload$data;
@@ -311,6 +327,7 @@ var ParticipantsService = exports.ParticipantsService = /*#__PURE__*/function ()
311
327
  /**
312
328
  * Updates when users were last active
313
329
  * @param userIds Users in most recent steps
330
+ * @example
314
331
  */
315
332
  (0, _defineProperty2.default)(this, "updateLastActive", function () {
316
333
  var userIds = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
@@ -319,6 +336,9 @@ var ParticipantsService = exports.ParticipantsService = /*#__PURE__*/function ()
319
336
  /**
320
337
  * Called when we receive a telepointer update from another
321
338
  * participant.
339
+ * @param payload
340
+ * @param thisSessionId
341
+ * @example
322
342
  */
323
343
  (0, _defineProperty2.default)(this, "onParticipantTelepointer", function (payload, thisSessionId) {
324
344
  var sessionId = payload.sessionId,
@@ -344,6 +364,7 @@ var ParticipantsService = exports.ParticipantsService = /*#__PURE__*/function ()
344
364
  * Every 5 minutes (PARTICIPANT_UPDATE_INTERVAL), removes inactive participants and emits the update to other participants.
345
365
  * Needs to be kicked off in the Provider.
346
366
  * @param sessionId SessionId from provider's connection
367
+ * @example
347
368
  */
348
369
  (0, _defineProperty2.default)(this, "startInactiveRemover", function (sessionId) {
349
370
  clearTimeout(_this.participantUpdateTimeout);
@@ -400,6 +421,7 @@ var ParticipantsService = exports.ParticipantsService = /*#__PURE__*/function ()
400
421
  *
401
422
  * if no {@link BatchProps#participantsLimit} is supplied
402
423
  * 1. Fetch users until there are no more participants to hydrate
424
+ * @example
403
425
  */
404
426
  (0, _defineProperty2.default)(this, "batchFetchUsers", /*#__PURE__*/(0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee4() {
405
427
  var _this$batchProps, debounceTime, participantsLimit, size;
@@ -463,8 +485,9 @@ var ParticipantsService = exports.ParticipantsService = /*#__PURE__*/function ()
463
485
  /**
464
486
  * We want to give some time for users to initially join before attempting to fetch users
465
487
  * otherwise we'll always make at least 2 calls if there's more than 1 participant
488
+ * @example
466
489
  */
467
- (0, _defineProperty2.default)(this, "batchFetchUsersWithDelay", /*#__PURE__*/(0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee5() {
490
+ (0, _defineProperty2.default)(this, "initializeFirstBatchFetchUsers", /*#__PURE__*/(0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee5() {
468
491
  return _regenerator.default.wrap(function _callee5$(_context5) {
469
492
  while (1) switch (_context5.prev = _context5.next) {
470
493
  case 0:
@@ -483,6 +506,8 @@ var ParticipantsService = exports.ParticipantsService = /*#__PURE__*/function ()
483
506
  })));
484
507
  /**
485
508
  * Keep list of participants up to date. Filter out inactive users etc.
509
+ * @param sessionId
510
+ * @example
486
511
  */
487
512
  (0, _defineProperty2.default)(this, "filterInactive", function (sessionId) {
488
513
  var now = Date.now();
@@ -500,6 +525,8 @@ var ParticipantsService = exports.ParticipantsService = /*#__PURE__*/function ()
500
525
  * Wrapper function to emit with error handling and analytics
501
526
  * @param data Data to emit
502
527
  * @param emit Emit function from Provider
528
+ * @param errorMessage
529
+ * @example
503
530
  */
504
531
  (0, _defineProperty2.default)(this, "emitPresence", function (data, errorMessage) {
505
532
  try {
@@ -518,6 +545,8 @@ var ParticipantsService = exports.ParticipantsService = /*#__PURE__*/function ()
518
545
  * Wrapper function to emit with error handling and analytics
519
546
  * @param data Data to emit
520
547
  * @param emit Emit function from Provider
548
+ * @param errorMessage
549
+ * @example
521
550
  */
522
551
  (0, _defineProperty2.default)(this, "emitTelepointer", function (data, errorMessage) {
523
552
  try {
@@ -530,6 +559,7 @@ var ParticipantsService = exports.ParticipantsService = /*#__PURE__*/function ()
530
559
  });
531
560
  /**
532
561
  * Used when the provider is disconnected or destroyed to prevent perpetual timers from continuously running
562
+ * @example
533
563
  */
534
564
  (0, _defineProperty2.default)(this, "clearTimers", function () {
535
565
  clearTimeout(_this.participantUpdateTimeout);
@@ -558,6 +588,8 @@ var ParticipantsService = exports.ParticipantsService = /*#__PURE__*/function ()
558
588
  * We keep track of participants internally, and emit the `presence` event to update
559
589
  * the active avatars in the editor.
560
590
  * This method will be triggered from backend to notify all participants to exchange presence
591
+ * @param payload
592
+ * @example
561
593
  */
562
594
  (0, _defineProperty2.default)(this, "onPresenceJoined", function (payload) {
563
595
  try {
@@ -575,6 +607,8 @@ var ParticipantsService = exports.ParticipantsService = /*#__PURE__*/function ()
575
607
  *
576
608
  * This will send both a 'presence' event and a 'participant:updated' event.
577
609
  * This updates both the avatars and the participants list.
610
+ * @param payload
611
+ * @example
578
612
  */
579
613
  (0, _defineProperty2.default)(this, "onPresence", function (payload) {
580
614
  try {
@@ -590,9 +624,6 @@ var ParticipantsService = exports.ParticipantsService = /*#__PURE__*/function ()
590
624
  (_this$analyticsHelper10 = _this.analyticsHelper) === null || _this$analyticsHelper10 === void 0 || _this$analyticsHelper10.sendErrorEvent(error, 'Error while receiving presence');
591
625
  }
592
626
  });
593
- /**
594
- *
595
- */
596
627
  (0, _defineProperty2.default)(this, "getParticipants", function () {
597
628
  return _this.participantsState.getParticipants();
598
629
  });
@@ -625,6 +656,7 @@ var ParticipantsService = exports.ParticipantsService = /*#__PURE__*/function ()
625
656
  /**
626
657
  * Called on receiving steps, emits each step's telepointer
627
658
  * @param steps Steps to extract telepointers from
659
+ * @example
628
660
  */
629
661
  function emitTelepointersFromSteps(steps) {
630
662
  var _this2 = this;
@@ -642,6 +674,7 @@ var ParticipantsService = exports.ParticipantsService = /*#__PURE__*/function ()
642
674
  * Wrapper function to emit with error handling and analytics
643
675
  * @param data Data to emit
644
676
  * @param errorMessage Error message for analytics
677
+ * @example
645
678
  */
646
679
  function emitPresenceActivityChange(data, errorMessage) {
647
680
  try {
@@ -47,8 +47,8 @@ var ParticipantsState = exports.ParticipantsState = /*#__PURE__*/(0, _createClas
47
47
  /**
48
48
  * A user may contain multiple sessions, only return the unique users based on aaid.
49
49
  * If multiple participants with same aaid exist, will return the most recent entry
50
- * @param param0
51
- * @returns
50
+ * @param filter additional filter to narrow down results
51
+ * @param filter.isHydrated
52
52
  */
53
53
  (0, _defineProperty2.default)(this, "getUniqueParticipants", function (_ref) {
54
54
  var _ref$isHydrated = _ref.isHydrated,
@@ -58,7 +58,11 @@ var ParticipantsState = exports.ParticipantsState = /*#__PURE__*/(0, _createClas
58
58
  participants.forEach(function (p) {
59
59
  if (!!p.isHydrated === isHydrated) {
60
60
  var previous = uniqueParticipants.get(p.userId);
61
- uniqueParticipants.set(p.userId, previous ? _objectSpread(_objectSpread({}, previous), p) : p);
61
+ uniqueParticipants.set(p.userId, previous ? _objectSpread(_objectSpread({}, previous), {}, {
62
+ lastActive: p.lastActive,
63
+ sessionId: p.sessionId,
64
+ presenceActivity: p.presenceActivity
65
+ }) : p);
62
66
  }
63
67
  });
64
68
  return (0, _toConsumableArray2.default)(uniqueParticipants.values());
@@ -74,7 +74,7 @@ var commitStepQueue = exports.commitStepQueue = function commitStepQueue(_ref) {
74
74
  // - doesn't impact any indexes,
75
75
  // - is setup for last write wins,
76
76
  // - and is just a boolean -- so no real risk of data loss.
77
- if (__livePage && (0, _platformFeatureFlags.fg)('platform.editor.live-pages-expand-divergence')) {
77
+ if (__livePage) {
78
78
  stepsWithClientAndUserId = stepsWithClientAndUserId.map(function (step) {
79
79
  if (isExpandChangeStep(step)) {
80
80
  // The title is also updated via this step, which we do want to send to the server.
@@ -152,7 +152,7 @@ var Provider = exports.Provider = /*#__PURE__*/function (_Emitter) {
152
152
  if (_this.config.getUser) {
153
153
  throw new _customErrors.ProviderInitialisationError('Cannot supply getUser and batchProps together');
154
154
  }
155
- _this.participantsService.batchFetchUsersWithDelay();
155
+ _this.participantsService.initializeFirstBatchFetchUsers();
156
156
  }
157
157
  _this.disconnectedAt = undefined;
158
158
  }).on('init', function (_ref3) {
@@ -330,9 +330,6 @@ var Provider = exports.Provider = /*#__PURE__*/function (_Emitter) {
330
330
  clearTimeout(_this.presenceUpdateTimeout);
331
331
  _this.participantsService.clearTimers();
332
332
  });
333
- /**
334
- *
335
- */
336
333
  (0, _defineProperty2.default)(_this, "getParticipants", function () {
337
334
  return _this.participantsService.getParticipants();
338
335
  });
@@ -359,7 +356,7 @@ var Provider = exports.Provider = /*#__PURE__*/function (_Emitter) {
359
356
  while (1) switch (_context4.prev = _context4.next) {
360
357
  case 0:
361
358
  if (!_this.config.batchProps) {
362
- _context4.next = 3;
359
+ _context4.next = 5;
363
360
  break;
364
361
  }
365
362
  _context4.next = 3;
@@ -367,8 +364,11 @@ var Provider = exports.Provider = /*#__PURE__*/function (_Emitter) {
367
364
  batchSize: (_props$fetchSize = props === null || props === void 0 ? void 0 : props.fetchSize) !== null && _props$fetchSize !== void 0 ? _props$fetchSize : _this.config.batchProps.batchSize
368
365
  }));
369
366
  case 3:
367
+ _context4.next = 6;
368
+ break;
369
+ case 5:
370
370
  throw new Error('Must provide batch properties to use fetchMore');
371
- case 4:
371
+ case 6:
372
372
  case "end":
373
373
  return _context4.stop();
374
374
  }
@@ -395,6 +395,7 @@ var Provider = exports.Provider = /*#__PURE__*/function (_Emitter) {
395
395
  _this.metadataService = new _metadataService.MetadataService(_this.emitCallback, _this.channel.sendMetadata);
396
396
  _this.namespaceService = new _namespaceService.NamespaceService();
397
397
  _this.presenceId = _this.config.presenceId;
398
+ _this.presenceActivity = _this.config.presenceActivity;
398
399
  if (config.isPresenceOnly) {
399
400
  // this check is specifically for the presence only
400
401
  // This presence feature is only for the confluence view page & jira presence which do not need the document service or api
@@ -434,8 +435,8 @@ var Provider = exports.Provider = /*#__PURE__*/function (_Emitter) {
434
435
  /**
435
436
  * Initialisation logic, called by the editor in the collab-edit plugin.
436
437
  *
437
- * @param {Object} options ...
438
438
  * @param {Function} options.getState Function that returns the editor state, used to retrieve collab-edit properties and to interact with prosemirror-collab
439
+ * @param options.editorApi
439
440
  * @param {SyncUpErrorFunction} options.onSyncUpError (Optional) Function that gets called when the sync of steps fails after retrying 30 times, used by Editor to log to analytics
440
441
  * @throws {ProviderInitialisationError} Something went wrong during provider initialisation
441
442
  */
@@ -500,9 +501,8 @@ var Provider = exports.Provider = /*#__PURE__*/function (_Emitter) {
500
501
  // Only used for the presence - opts out of the document service and api service
501
502
  }, {
502
503
  key: "setupForPresenceOnly",
503
- value: function setupForPresenceOnly(clientId, presenceActivity) {
504
+ value: function setupForPresenceOnly(clientId) {
504
505
  this.clientId = clientId;
505
- this.presenceActivity = presenceActivity;
506
506
  this.checkForCookies();
507
507
  try {
508
508
  if (!this.isChannelInitialized) {
@@ -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 = "10.14.4";
8
+ var version = exports.version = "10.16.0";
9
9
  var nextMajorVersion = exports.nextMajorVersion = function nextMajorVersion() {
10
10
  return [Number(version.split('.')[0]) + 1, 0, 0].join('.');
11
11
  };
@@ -11,6 +11,8 @@ import Network from './connectivity/network';
11
11
  import { INTERNAL_ERROR_CODE } from './errors/internal-errors';
12
12
  import { NCS_ERROR_CODE } from './errors/ncs-errors';
13
13
  import { NotConnectedError, NotInitializedError } from './errors/custom-errors';
14
+ import FeatureGates from '@atlaskit/feature-gate-js-client';
15
+ import { bind } from 'bind-event-listener';
14
16
  const logger = createLogger('Channel', 'green');
15
17
  export class Channel extends Emitter {
16
18
  constructor(config, analyticsHelper) {
@@ -299,6 +301,40 @@ export class Channel extends Emitter {
299
301
  (_this$socket3 = this.socket) === null || _this$socket3 === void 0 ? void 0 : _this$socket3.connect();
300
302
  }
301
303
  });
304
+ /**
305
+ * Unbinds event listeners and timers used when handling connection auto-close when tab is hidden
306
+ */
307
+ _defineProperty(this, "cleanupAutoDisconnect", () => {
308
+ this.unbindVisibilityListener();
309
+ if (this.disconnectTimer) {
310
+ clearTimeout(this.disconnectTimer);
311
+ this.disconnectTimer = undefined;
312
+ }
313
+ });
314
+ /**
315
+ * Cleanup the visiblitychange listener upon Collab Provider destroy
316
+ * Value set when the listener is binded in addVisiblityListener
317
+ */
318
+ _defineProperty(this, "unbindVisibilityListener", () => {});
319
+ _defineProperty(this, "autoDisconnect", (disconnectTimer, disconnectDelay) => {
320
+ if (document.hidden) {
321
+ logger('visibilitychange: hidden');
322
+ return setTimeout(() => {
323
+ var _this$socket4;
324
+ logger('visibilitychange: closing connection');
325
+ (_this$socket4 = this.socket) === null || _this$socket4 === void 0 ? void 0 : _this$socket4.close();
326
+ }, disconnectDelay || disconnectDelay === 0 ? disconnectDelay * 1000 : 60 * 1000);
327
+ } else {
328
+ var _this$socket5;
329
+ logger('visibilitychange: visible');
330
+ if (disconnectTimer) {
331
+ clearTimeout(disconnectTimer);
332
+ }
333
+ logger('visibilitychange: re-connecting');
334
+ (_this$socket5 = this.socket) === null || _this$socket5 === void 0 ? void 0 : _this$socket5.connect();
335
+ return;
336
+ }
337
+ });
302
338
  this.config = config;
303
339
  this.analyticsHelper = analyticsHelper;
304
340
  this.initExperience = createDocInitExp(this.analyticsHelper);
@@ -480,6 +516,10 @@ export class Channel extends Emitter {
480
516
  this.reconnectHelper = new ReconnectHelper();
481
517
  // Fired upon a reconnection attempt error (from Socket.IO Manager)
482
518
  this.socket.io.on('reconnect_error', this.onReconnectError);
519
+
520
+ // Automatic disconnect on tab background with delay, to keep presence UX accurate
521
+ // and reduce wasted connections. Reconnects upon tab becoming active again
522
+ this.addVisiblityListener();
483
523
  }
484
524
 
485
525
  // Ignored via go/ees005
@@ -546,9 +586,26 @@ export class Channel extends Emitter {
546
586
  isLimitExceeded(stepLimit, stepSizeLimit, maxStepSizeLimit) {
547
587
  return stepLimit > 0 && this.stepCounter > stepLimit || stepSizeLimit > 0 && this.stepSizeCounter > stepSizeLimit || maxStepSizeLimit > 0 && this.maxStepSize > maxStepSizeLimit;
548
588
  }
589
+ /**
590
+ * Adds an event listener for visibilitychange events to handle auto-disconnection if tab is in background
591
+ */
592
+ addVisiblityListener() {
593
+ var _FeatureGates$getExpe;
594
+ const disconnectDelay = (_FeatureGates$getExpe = FeatureGates.getExperimentValue('platform_editor_connection_auto_disconnect_delay', 'delay', -1)) !== null && _FeatureGates$getExpe !== void 0 ? _FeatureGates$getExpe : -1;
595
+ const isAutoDisconnectEnabled = disconnectDelay >= 0;
596
+ if (isAutoDisconnectEnabled && this.config.isPresenceOnly) {
597
+ this.unbindVisibilityListener = bind(document, {
598
+ type: 'visibilitychange',
599
+ listener: () => {
600
+ this.disconnectTimer = this.autoDisconnect(this.disconnectTimer, disconnectDelay);
601
+ }
602
+ });
603
+ }
604
+ }
549
605
  disconnect() {
550
606
  var _this$network;
551
607
  this.unsubscribeAll();
608
+ this.cleanupAutoDisconnect();
552
609
  (_this$network = this.network) === null || _this$network === void 0 ? void 0 : _this$network.destroy();
553
610
  this.network = null;
554
611
  if (this.socket) {
@@ -30,9 +30,22 @@ export const createParticipantFromPayload = async (payload, getUser) => {
30
30
  };
31
31
  return participant;
32
32
  };
33
+
34
+ /**
35
+ * Will use the getUsers callback from batchProps to fetch users
36
+ *
37
+ * 1. Determine all the participants that need to be hydrated
38
+ * 2. Only fetch a subset of those participants based on batchSize
39
+ * 3. Of the users fetched, find all of those users' sessions and mark those entries as hydrated
40
+ *
41
+ * @param participantsState
42
+ * @param batchProps
43
+ * @returns
44
+ * @example
45
+ */
33
46
  export const fetchParticipants = async (participantsState, batchProps) => {
34
47
  const {
35
- batchSize = batchProps.batchSize || DEFAULT_BATCH_FETCH_SIZE,
48
+ batchSize = DEFAULT_BATCH_FETCH_SIZE,
36
49
  getUsers
37
50
  } = batchProps;
38
51
  const participantsToHydrate = participantsState.getUniqueParticipants({
@@ -54,24 +67,22 @@ export const fetchParticipants = async (participantsState, batchProps) => {
54
67
  const {
55
68
  sessionId
56
69
  } = participant;
57
- if (participant && sessionId) {
58
- const hydratedParticipant = {
59
- name: (user === null || user === void 0 ? void 0 : user.name) || '',
60
- avatar: (user === null || user === void 0 ? void 0 : user.avatar) || '',
61
- email: (user === null || user === void 0 ? void 0 : user.email) || '',
62
- sessionId,
63
- lastActive: participant.lastActive,
64
- userId: user.userId,
65
- clientId: participant.clientId,
66
- permit: participant.permit,
67
- isGuest: user === null || user === void 0 ? void 0 : user.isGuest,
68
- presenceId: participant.presenceId,
69
- presenceActivity: participant.presenceActivity,
70
- isHydrated: true
71
- };
72
- participantsState.setBySessionId(sessionId, hydratedParticipant);
73
- hydratedParticipants.push(hydratedParticipant);
74
- }
70
+ const hydratedParticipant = {
71
+ name: user.name,
72
+ avatar: user.avatar,
73
+ email: user.email,
74
+ userId: user.userId,
75
+ isGuest: user.isGuest,
76
+ sessionId,
77
+ lastActive: participant.lastActive,
78
+ clientId: participant.clientId,
79
+ permit: participant.permit,
80
+ presenceId: participant.presenceId,
81
+ presenceActivity: participant.presenceActivity,
82
+ isHydrated: true
83
+ };
84
+ participantsState.setBySessionId(sessionId, hydratedParticipant);
85
+ hydratedParticipants.push(hydratedParticipant);
75
86
  });
76
87
  });
77
88
  return hydratedParticipants;