@atlaskit/collab-provider 8.2.0 → 8.3.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 (67) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/cjs/analytics/index.js +66 -9
  3. package/dist/cjs/analytics/performance.js +45 -35
  4. package/dist/cjs/analytics/ufo.js +33 -0
  5. package/dist/cjs/channel.js +89 -54
  6. package/dist/cjs/connectivity/network.js +53 -0
  7. package/dist/cjs/connectivity/reconnect-helper.js +48 -0
  8. package/dist/cjs/connectivity/singleton.js +15 -0
  9. package/dist/cjs/disconnected-reason-mapper.js +19 -2
  10. package/dist/cjs/error-code-mapper.js +16 -1
  11. package/dist/cjs/helpers/const.js +5 -10
  12. package/dist/cjs/provider/catchup.js +8 -12
  13. package/dist/cjs/provider/commit-step.js +66 -0
  14. package/dist/cjs/provider/index.js +480 -558
  15. package/dist/cjs/provider/telepointers.js +78 -0
  16. package/dist/cjs/version-wrapper.js +1 -1
  17. package/dist/cjs/version.json +1 -1
  18. package/dist/es2019/analytics/index.js +57 -8
  19. package/dist/es2019/analytics/performance.js +46 -35
  20. package/dist/es2019/analytics/ufo.js +22 -0
  21. package/dist/es2019/channel.js +89 -58
  22. package/dist/es2019/connectivity/network.js +34 -0
  23. package/dist/es2019/connectivity/reconnect-helper.js +29 -0
  24. package/dist/es2019/connectivity/singleton.js +7 -0
  25. package/dist/es2019/disconnected-reason-mapper.js +17 -1
  26. package/dist/es2019/error-code-mapper.js +16 -1
  27. package/dist/es2019/helpers/const.js +4 -7
  28. package/dist/es2019/provider/catchup.js +5 -12
  29. package/dist/es2019/provider/commit-step.js +53 -0
  30. package/dist/es2019/provider/index.js +397 -496
  31. package/dist/es2019/provider/telepointers.js +65 -0
  32. package/dist/es2019/version-wrapper.js +1 -1
  33. package/dist/es2019/version.json +1 -1
  34. package/dist/esm/analytics/index.js +67 -9
  35. package/dist/esm/analytics/performance.js +46 -35
  36. package/dist/esm/analytics/ufo.js +25 -0
  37. package/dist/esm/channel.js +89 -56
  38. package/dist/esm/connectivity/network.js +45 -0
  39. package/dist/esm/connectivity/reconnect-helper.js +42 -0
  40. package/dist/esm/connectivity/singleton.js +7 -0
  41. package/dist/esm/disconnected-reason-mapper.js +17 -1
  42. package/dist/esm/error-code-mapper.js +16 -1
  43. package/dist/esm/helpers/const.js +4 -7
  44. package/dist/esm/provider/catchup.js +8 -12
  45. package/dist/esm/provider/commit-step.js +58 -0
  46. package/dist/esm/provider/index.js +482 -559
  47. package/dist/esm/provider/telepointers.js +69 -0
  48. package/dist/esm/version-wrapper.js +1 -1
  49. package/dist/esm/version.json +1 -1
  50. package/dist/types/analytics/index.d.ts +8 -2
  51. package/dist/types/analytics/performance.d.ts +5 -5
  52. package/dist/types/analytics/ufo.d.ts +3 -0
  53. package/dist/types/channel.d.ts +11 -5
  54. package/dist/types/connectivity/network.d.ts +17 -0
  55. package/dist/types/connectivity/reconnect-helper.d.ts +8 -0
  56. package/dist/types/connectivity/singleton.d.ts +3 -0
  57. package/dist/types/disconnected-reason-mapper.d.ts +1 -0
  58. package/dist/types/error-code-mapper.d.ts +5 -1
  59. package/dist/types/helpers/const.d.ts +109 -25
  60. package/dist/types/provider/commit-step.d.ts +14 -0
  61. package/dist/types/provider/index.d.ts +9 -5
  62. package/dist/types/provider/telepointers.d.ts +5 -0
  63. package/dist/types/socket-io-provider.d.ts +2 -1
  64. package/dist/types/types.d.ts +27 -10
  65. package/package.json +4 -5
  66. package/report.api.md +29 -0
  67. package/.vscode/settings.json +0 -3
@@ -0,0 +1,65 @@
1
+ import { createLogger } from '../helpers/utils';
2
+ import { ExperiencePerformanceTypes, ExperienceTypes, UFOExperience } from '@atlaskit/ufo';
3
+ import { AcknowledgementResponseTypes } from '../types';
4
+ const logger = createLogger('Telepointer', 'green');
5
+ export const telepointersFromStep = (participants, step) => {
6
+ const [participant] = Array.from(participants.values()).filter(p => p.clientId === step.clientId);
7
+ if (participant) {
8
+ var _node$text;
9
+ const {
10
+ stepType,
11
+ to,
12
+ from,
13
+ slice = {
14
+ content: []
15
+ }
16
+ } = step;
17
+ const [node] = slice.content;
18
+ if (to && from && stepType === 'replace' && to === from && slice.content.length === 1 && (node === null || node === void 0 ? void 0 : node.type) === 'text' && (node === null || node === void 0 ? void 0 : (_node$text = node.text) === null || _node$text === void 0 ? void 0 : _node$text.length) === 1) {
19
+ return {
20
+ sessionId: participant.sessionId,
21
+ selection: {
22
+ type: 'textSelection',
23
+ anchor: from + 1,
24
+ head: to + 1
25
+ },
26
+ type: 'telepointer'
27
+ };
28
+ }
29
+ }
30
+ };
31
+ export const telepointerCallback = documentAri => {
32
+ const telepointerExperience = new UFOExperience('collab-provider.telepointer', {
33
+ type: ExperienceTypes.Operation,
34
+ performanceType: ExperiencePerformanceTypes.Custom,
35
+ performanceConfig: {
36
+ histogram: {
37
+ [ExperiencePerformanceTypes.Custom]: {
38
+ duration: '250_500_1000_1500_2000_3000_4000'
39
+ }
40
+ }
41
+ }
42
+ });
43
+ telepointerExperience.addMetadata({
44
+ documentAri
45
+ });
46
+ telepointerExperience.start();
47
+ return response => {
48
+ if (response.type === AcknowledgementResponseTypes.SUCCESS) {
49
+ telepointerExperience.success();
50
+ } else if (response.type === AcknowledgementResponseTypes.ERROR) {
51
+ const errorMessage = response.error;
52
+ telepointerExperience.addMetadata({
53
+ error: errorMessage
54
+ });
55
+ logger('Error from collab service with telepointer broadcast', errorMessage);
56
+ telepointerExperience.failure();
57
+ } else {
58
+ logger('Invalid ACK from collab service with telepointer broadcast');
59
+ telepointerExperience.addMetadata({
60
+ error: 'Invalid ACK from collab service with telepointer broadcast'
61
+ });
62
+ telepointerExperience.failure();
63
+ }
64
+ };
65
+ };
@@ -1,5 +1,5 @@
1
1
  export const name = "@atlaskit/collab-provider";
2
- export const version = "8.2.0";
2
+ export const version = "8.3.0";
3
3
  export const nextMajorVersion = () => {
4
4
  return [Number(version.split('.')[0]) + 1, 0, 0].join('.');
5
5
  };
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@atlaskit/collab-provider",
3
- "version": "8.2.0",
3
+ "version": "8.3.0",
4
4
  "sideEffects": false
5
5
  }
@@ -1,9 +1,18 @@
1
+ import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
2
+ import _createClass from "@babel/runtime/helpers/createClass";
1
3
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
4
  function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
3
5
  function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
4
- import { EVENT_SUBJECT, COLLAB_SERVICE } from '../helpers/const';
6
+ import { EVENT_ACTION } from '../helpers/const';
5
7
  import { name as packageName, version as packageVersion } from '../version-wrapper';
6
- export var triggerAnalyticsEvent = function triggerAnalyticsEvent(analyticsEvent, analyticsClient) {
8
+ import { network } from '../connectivity/singleton';
9
+ var EVENT_SUBJECT = 'collab';
10
+ var COLLAB_SERVICE;
11
+ (function (COLLAB_SERVICE) {
12
+ COLLAB_SERVICE["NCS"] = "ncs";
13
+ COLLAB_SERVICE["SYNCHRONY"] = "synchrony";
14
+ })(COLLAB_SERVICE || (COLLAB_SERVICE = {}));
15
+ var triggerAnalyticsEvent = function triggerAnalyticsEvent(analyticsEvent, analyticsClient) {
7
16
  if (!analyticsClient) {
8
17
  return;
9
18
  }
@@ -12,18 +21,67 @@ export var triggerAnalyticsEvent = function triggerAnalyticsEvent(analyticsEvent
12
21
  attributes: _objectSpread({
13
22
  packageName: packageName,
14
23
  packageVersion: packageVersion,
15
- collabService: COLLAB_SERVICE.NCS
24
+ collabService: COLLAB_SERVICE.NCS,
25
+ network: {
26
+ status: network.getStatus()
27
+ }
16
28
  }, analyticsEvent.attributes),
17
29
  tags: ['editor'],
18
30
  action: analyticsEvent.eventAction,
19
31
  source: 'unknown' // Adds zero analytics value, but event validation throws an error if you don't add it :-(
20
32
  };
21
33
 
34
+ if (analyticsEvent.eventAction === EVENT_ACTION.ERROR) {
35
+ payload.nonPrivacySafeAttributes = analyticsEvent.nonPrivacySafeAttributes;
36
+ }
37
+
22
38
  // Let the browser figure out
23
39
  // when it should send those events
24
- var requestIdleCallbackFunction = window.requestIdleCallback;
25
- var runItLater = typeof requestIdleCallbackFunction === 'function' ? requestIdleCallbackFunction : window.requestAnimationFrame;
26
- runItLater(function () {
27
- analyticsClient.sendOperationalEvent(payload);
28
- });
29
- };
40
+ try {
41
+ var requestIdleCallbackFunction = window.requestIdleCallback;
42
+ var runItLater = typeof requestIdleCallbackFunction === 'function' ? requestIdleCallbackFunction : window.requestAnimationFrame;
43
+ runItLater(function () {
44
+ analyticsClient.sendOperationalEvent(payload);
45
+ });
46
+ } catch (error) {
47
+ // silently fail for now https://product-fabric.atlassian.net/browse/ESS-3112
48
+ }
49
+ };
50
+ var AnalyticsHelper = /*#__PURE__*/function () {
51
+ function AnalyticsHelper(documentAri, analyticsClient) {
52
+ _classCallCheck(this, AnalyticsHelper);
53
+ this.analyticsClient = analyticsClient;
54
+ this.documentAri = documentAri;
55
+ }
56
+ _createClass(AnalyticsHelper, [{
57
+ key: "sendErrorEvent",
58
+ value: function sendErrorEvent(error, errorMessage) {
59
+ var errorAnalyticsEvent = {
60
+ eventAction: EVENT_ACTION.ERROR,
61
+ attributes: {
62
+ documentAri: this.documentAri,
63
+ errorMessage: errorMessage
64
+ },
65
+ nonPrivacySafeAttributes: {
66
+ error: error
67
+ }
68
+ };
69
+ triggerAnalyticsEvent(errorAnalyticsEvent, this.analyticsClient);
70
+ }
71
+ }, {
72
+ key: "sendActionEvent",
73
+ value: function sendActionEvent(action, status, attributes // This breaks discriminated unions, because there is no obvious field to discriminate against any more
74
+ ) {
75
+ var analyticsEvent = {
76
+ eventAction: action,
77
+ attributes: _objectSpread({
78
+ documentAri: this.documentAri,
79
+ eventStatus: status
80
+ }, attributes)
81
+ };
82
+ triggerAnalyticsEvent(analyticsEvent, this.analyticsClient);
83
+ }
84
+ }]);
85
+ return AnalyticsHelper;
86
+ }();
87
+ export { AnalyticsHelper as default };
@@ -2,8 +2,8 @@ export var MEASURE_NAME;
2
2
  (function (MEASURE_NAME) {
3
3
  MEASURE_NAME["SOCKET_CONNECT"] = "socketConnect";
4
4
  MEASURE_NAME["DOCUMENT_INIT"] = "documentInit";
5
- MEASURE_NAME["CONVERT_PM_TO_ADF"] = "convertPMToADF";
6
5
  MEASURE_NAME["COMMIT_UNCONFIRMED_STEPS"] = "commitUnconfirmedSteps";
6
+ MEASURE_NAME["PUBLISH_PAGE"] = "publishPage";
7
7
  })(MEASURE_NAME || (MEASURE_NAME = {}));
8
8
  var isPerformanceAPIAvailable = function isPerformanceAPIAvailable() {
9
9
  return typeof window !== 'undefined' && 'performance' in window && ['measure', 'clearMeasures', 'clearMarks', 'getEntriesByName', 'getEntriesByType'].every(function (api) {
@@ -12,47 +12,58 @@ var isPerformanceAPIAvailable = function isPerformanceAPIAvailable() {
12
12
  };
13
13
  var hasPerformanceAPIAvailable = isPerformanceAPIAvailable();
14
14
  var measureMap = new Map();
15
- export function startMeasure(measureName) {
16
- if (!hasPerformanceAPIAvailable) {
17
- return;
15
+ export function startMeasure(measureName, analyticsHelper) {
16
+ try {
17
+ if (!hasPerformanceAPIAvailable) {
18
+ return;
19
+ }
20
+ performance.mark("".concat(measureName, "::start"));
21
+ measureMap.set(measureName, performance.now());
22
+ } catch (error) {
23
+ analyticsHelper === null || analyticsHelper === void 0 ? void 0 : analyticsHelper.sendErrorEvent(error, 'Error while measuring performance when marking the start');
18
24
  }
19
- performance.mark("".concat(measureName, "::start"));
20
- measureMap.set(measureName, performance.now());
21
25
  }
22
- export function stopMeasure(measureName, onMeasureComplete) {
23
- if (!hasPerformanceAPIAvailable) {
24
- return;
25
- }
26
-
27
- // `startMeasure` is not called with `measureName` before.
28
- if (!measureMap.get(measureName)) {
29
- return;
30
- }
31
- performance.mark("".concat(measureName, "::end"));
32
- var start = onMeasureComplete ? measureMap.get(measureName) : undefined;
26
+ export function stopMeasure(measureName, analyticsHelper, onMeasureComplete) {
27
+ var start;
33
28
  try {
29
+ if (!hasPerformanceAPIAvailable) {
30
+ return;
31
+ }
32
+
33
+ // `startMeasure` is not called with `measureName` before.
34
+ if (!measureMap.get(measureName)) {
35
+ return;
36
+ }
37
+ performance.mark("".concat(measureName, "::end"));
38
+ start = onMeasureComplete ? measureMap.get(measureName) : undefined;
34
39
  performance.measure(measureName, "".concat(measureName, "::start"), "".concat(measureName, "::end"));
35
- } catch (e) {}
36
- var entry = performance.getEntriesByName(measureName).pop();
37
- clearMeasure(measureName);
38
- var measure;
39
- if (entry) {
40
- measure = {
41
- duration: entry.duration,
42
- startTime: entry.startTime
43
- };
44
- } else if (start) {
45
- measure = {
46
- duration: performance.now() - start,
47
- startTime: start
48
- };
40
+ } catch (error) {
41
+ analyticsHelper === null || analyticsHelper === void 0 ? void 0 : analyticsHelper.sendErrorEvent(error, 'Error while measuring performance when marking the end');
49
42
  }
50
- if (measure && onMeasureComplete) {
51
- onMeasureComplete(measure.duration, measure.startTime);
43
+ try {
44
+ var entry = performance.getEntriesByName(measureName).pop();
45
+ clearMeasure(measureName);
46
+ var measure;
47
+ if (entry) {
48
+ measure = {
49
+ duration: entry.duration,
50
+ startTime: entry.startTime
51
+ };
52
+ } else if (start) {
53
+ measure = {
54
+ duration: performance.now() - start,
55
+ startTime: start
56
+ };
57
+ }
58
+ if (measure && onMeasureComplete) {
59
+ onMeasureComplete(measure.duration, measure.startTime);
60
+ }
61
+ return measure;
62
+ } catch (error) {
63
+ analyticsHelper === null || analyticsHelper === void 0 ? void 0 : analyticsHelper.sendErrorEvent(error, 'Error while measuring performance when completing the measurement');
52
64
  }
53
- return measure;
54
65
  }
55
- export function clearMeasure(measureName) {
66
+ function clearMeasure(measureName) {
56
67
  if (!hasPerformanceAPIAvailable) {
57
68
  return;
58
69
  }
@@ -0,0 +1,25 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+ import { ExperiencePerformanceTypes, ExperienceTypes, UFOExperience } from '@atlaskit/ufo';
3
+ var createDocumentInitExperience = function createDocumentInitExperience() {
4
+ return new UFOExperience('collab-provider.document-init', {
5
+ type: ExperienceTypes.Load,
6
+ performanceType: ExperiencePerformanceTypes.Custom,
7
+ performanceConfig: {
8
+ histogram: _defineProperty({}, ExperiencePerformanceTypes.Custom, {
9
+ duration: '250_500_1000_1500_2000_3000_4000'
10
+ })
11
+ }
12
+ });
13
+ };
14
+ var withErrorHandling = function withErrorHandling(createExperience, analyticsHelper) {
15
+ var initExperience;
16
+ try {
17
+ initExperience = createExperience();
18
+ } catch (error) {
19
+ analyticsHelper === null || analyticsHelper === void 0 ? void 0 : analyticsHelper.sendErrorEvent(error, 'Error while initialising a UFO experience');
20
+ }
21
+ return initExperience;
22
+ };
23
+ export var createDocInitExp = function createDocInitExp(analyticsHelper) {
24
+ return withErrorHandling(createDocumentInitExperience, analyticsHelper);
25
+ };
@@ -12,35 +12,27 @@ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { va
12
12
  function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }
13
13
  function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }
14
14
  import { utils } from '@atlaskit/util-service-support';
15
- // TODO: Validate if this is actually equivalent to the AnalyticsWebClient in @atlassiansox/analytics-web-client
16
-
17
15
  import { Emitter } from './emitter';
18
16
  import { ErrorCodeMapper } from './error-code-mapper';
19
17
  import { createLogger, getProduct, getSubProduct } from './helpers/utils';
20
18
  import { MEASURE_NAME, startMeasure, stopMeasure } from './analytics/performance';
21
- import { triggerAnalyticsEvent } from './analytics';
22
19
  import { EVENT_ACTION, EVENT_STATUS } from './helpers/const';
23
- import { ExperiencePerformanceTypes, ExperienceTypes, UFOExperience } from '@atlaskit/ufo';
20
+ import ReconnectHelper from './connectivity/reconnect-helper';
21
+ import { createDocInitExp } from './analytics/ufo';
22
+ import Network from './connectivity/network';
24
23
  var logger = createLogger('Channel', 'green');
25
24
  export var Channel = /*#__PURE__*/function (_Emitter) {
26
25
  _inherits(Channel, _Emitter);
27
26
  var _super = _createSuper(Channel);
28
- function Channel(config) {
27
+ function Channel(config, analyticsHelper) {
29
28
  var _this;
30
29
  _classCallCheck(this, Channel);
31
30
  _this = _super.call(this);
32
31
  _defineProperty(_assertThisInitialized(_this), "connected", false);
33
32
  _defineProperty(_assertThisInitialized(_this), "socket", null);
33
+ _defineProperty(_assertThisInitialized(_this), "reconnectHelper", null);
34
34
  _defineProperty(_assertThisInitialized(_this), "initialized", false);
35
- _defineProperty(_assertThisInitialized(_this), "initExperience", new UFOExperience('collab-provider.document-init', {
36
- type: ExperienceTypes.Load,
37
- performanceType: ExperiencePerformanceTypes.Custom,
38
- performanceConfig: {
39
- histogram: _defineProperty({}, ExperiencePerformanceTypes.Custom, {
40
- duration: '250_500_1000_1500_2000_3000_4000'
41
- })
42
- }
43
- }));
35
+ _defineProperty(_assertThisInitialized(_this), "network", null);
44
36
  _defineProperty(_assertThisInitialized(_this), "getInitialized", function () {
45
37
  return _this.initialized;
46
38
  });
@@ -51,16 +43,12 @@ export var Channel = /*#__PURE__*/function (_Emitter) {
51
43
  return _this.socket;
52
44
  });
53
45
  _defineProperty(_assertThisInitialized(_this), "onConnectError", function (error) {
54
- var measure = stopMeasure(MEASURE_NAME.SOCKET_CONNECT);
55
- triggerAnalyticsEvent({
56
- eventAction: EVENT_ACTION.CONNECTION,
57
- attributes: {
58
- eventStatus: EVENT_STATUS.FAILURE,
59
- error: error,
60
- latency: measure === null || measure === void 0 ? void 0 : measure.duration,
61
- documentAri: _this.config.documentAri
62
- }
63
- }, _this.analyticsClient);
46
+ var _this$analyticsHelper, _this$analyticsHelper2;
47
+ var measure = stopMeasure(MEASURE_NAME.SOCKET_CONNECT, _this.analyticsHelper);
48
+ (_this$analyticsHelper = _this.analyticsHelper) === null || _this$analyticsHelper === void 0 ? void 0 : _this$analyticsHelper.sendActionEvent(EVENT_ACTION.CONNECTION, EVENT_STATUS.FAILURE, {
49
+ latency: measure === null || measure === void 0 ? void 0 : measure.duration
50
+ });
51
+ (_this$analyticsHelper2 = _this.analyticsHelper) === null || _this$analyticsHelper2 === void 0 ? void 0 : _this$analyticsHelper2.sendErrorEvent(error, 'Error while establishing connection');
64
52
  // If error received with `data`, it means the connection is rejected
65
53
  // by the server on purpose for example no permission, so no need to
66
54
  // keep the underneath connection, need to close. But some error like
@@ -74,18 +62,29 @@ export var Channel = /*#__PURE__*/function (_Emitter) {
74
62
  data: error.data
75
63
  });
76
64
  });
65
+ _defineProperty(_assertThisInitialized(_this), "onReconnectError", function (error) {
66
+ var _this$reconnectHelper, _this$reconnectHelper2;
67
+ (_this$reconnectHelper = _this.reconnectHelper) === null || _this$reconnectHelper === void 0 ? void 0 : _this$reconnectHelper.countReconnectError();
68
+ if ((_this$reconnectHelper2 = _this.reconnectHelper) !== null && _this$reconnectHelper2 !== void 0 && _this$reconnectHelper2.isLikelyNetworkIssue()) {
69
+ var _this$analyticsHelper3;
70
+ (_this$analyticsHelper3 = _this.analyticsHelper) === null || _this$analyticsHelper3 === void 0 ? void 0 : _this$analyticsHelper3.sendErrorEvent(error, 'Likely network issue while reconnecting the channel');
71
+ _this.emit('error', {
72
+ message: 'Reconnection failed 8 times when browser was offline, likely there was a network issue.',
73
+ data: {
74
+ status: 400,
75
+ code: 'RECONNECTION_NETWORK_ISSUE'
76
+ }
77
+ });
78
+ }
79
+ });
77
80
  _defineProperty(_assertThisInitialized(_this), "onConnect", function () {
81
+ var _this$analyticsHelper4;
78
82
  _this.connected = true;
79
83
  logger('Connected.', _this.socket.id);
80
- var measure = stopMeasure(MEASURE_NAME.SOCKET_CONNECT);
81
- triggerAnalyticsEvent({
82
- eventAction: EVENT_ACTION.CONNECTION,
83
- attributes: {
84
- eventStatus: EVENT_STATUS.SUCCESS,
85
- latency: measure === null || measure === void 0 ? void 0 : measure.duration,
86
- documentAri: _this.config.documentAri
87
- }
88
- }, _this.analyticsClient);
84
+ var measure = stopMeasure(MEASURE_NAME.SOCKET_CONNECT, _this.analyticsHelper);
85
+ (_this$analyticsHelper4 = _this.analyticsHelper) === null || _this$analyticsHelper4 === void 0 ? void 0 : _this$analyticsHelper4.sendActionEvent(EVENT_ACTION.CONNECTION, EVENT_STATUS.SUCCESS, {
86
+ latency: measure === null || measure === void 0 ? void 0 : measure.duration
87
+ });
89
88
  _this.emit('connected', {
90
89
  sid: _this.socket.id,
91
90
  initialized: _this.initialized
@@ -96,19 +95,16 @@ export var Channel = /*#__PURE__*/function (_Emitter) {
96
95
  logger('Session ID is', _this.socket.id);
97
96
  if (data.type === 'initial') {
98
97
  if (!_this.initialized) {
99
- var measure = stopMeasure(MEASURE_NAME.DOCUMENT_INIT);
100
- _this.initExperience.success();
101
- triggerAnalyticsEvent({
102
- eventAction: EVENT_ACTION.DOCUMENT_INIT,
103
- attributes: {
104
- eventStatus: EVENT_STATUS.SUCCESS,
105
- // TODO: detect when document init fails and fire corresponding event for it
106
- latency: measure === null || measure === void 0 ? void 0 : measure.duration,
107
- documentAri: _this.config.documentAri,
108
- requiredPageRecovery: data === null || data === void 0 ? void 0 : data.requiredPageRecovery,
109
- ttlEnabled: data === null || data === void 0 ? void 0 : data.ttlEnabled
110
- }
111
- }, _this.analyticsClient);
98
+ var _this$initExperience, _this$analyticsHelper5;
99
+ var measure = stopMeasure(MEASURE_NAME.DOCUMENT_INIT, _this.analyticsHelper);
100
+ (_this$initExperience = _this.initExperience) === null || _this$initExperience === void 0 ? void 0 : _this$initExperience.success();
101
+ (_this$analyticsHelper5 = _this.analyticsHelper) === null || _this$analyticsHelper5 === void 0 ? void 0 : _this$analyticsHelper5.sendActionEvent(EVENT_ACTION.DOCUMENT_INIT,
102
+ // TODO: detect when document init fails and fire corresponding event for it
103
+ EVENT_STATUS.SUCCESS, {
104
+ latency: measure === null || measure === void 0 ? void 0 : measure.duration,
105
+ resetReason: data === null || data === void 0 ? void 0 : data.resetReason,
106
+ ttlEnabled: data === null || data === void 0 ? void 0 : data.ttlEnabled
107
+ });
112
108
  var doc = data.doc,
113
109
  version = data.version,
114
110
  userId = data.userId,
@@ -137,10 +133,17 @@ export var Channel = /*#__PURE__*/function (_Emitter) {
137
133
  _this.emit('steps:added', data);
138
134
  }
139
135
  });
136
+ _defineProperty(_assertThisInitialized(_this), "onOnlineHandler", function () {
137
+ // Force an immediate reconnect, the socket must first be closed to reset reconnection delay logic
138
+ if (!_this.connected) {
139
+ var _this$socket2, _this$socket3;
140
+ (_this$socket2 = _this.socket) === null || _this$socket2 === void 0 ? void 0 : _this$socket2.close();
141
+ (_this$socket3 = _this.socket) === null || _this$socket3 === void 0 ? void 0 : _this$socket3.connect();
142
+ }
143
+ });
140
144
  _this.config = config;
141
- if (config.analyticsClient) {
142
- _this.analyticsClient = config.analyticsClient;
143
- }
145
+ _this.analyticsHelper = analyticsHelper;
146
+ _this.initExperience = createDocInitExp(_this.analyticsHelper);
144
147
  return _this;
145
148
  }
146
149
 
@@ -153,10 +156,11 @@ export var Channel = /*#__PURE__*/function (_Emitter) {
153
156
  */
154
157
  function connect() {
155
158
  var _this2 = this;
156
- startMeasure(MEASURE_NAME.SOCKET_CONNECT);
159
+ startMeasure(MEASURE_NAME.SOCKET_CONNECT, this.analyticsHelper);
157
160
  if (!this.initialized) {
158
- startMeasure(MEASURE_NAME.DOCUMENT_INIT);
159
- this.initExperience.start();
161
+ var _this$initExperience2;
162
+ startMeasure(MEASURE_NAME.DOCUMENT_INIT, this.analyticsHelper);
163
+ (_this$initExperience2 = this.initExperience) === null || _this$initExperience2 === void 0 ? void 0 : _this$initExperience2.start();
160
164
  }
161
165
  var _this$config = this.config,
162
166
  documentAri = _this$config.documentAri,
@@ -232,6 +236,7 @@ export var Channel = /*#__PURE__*/function (_Emitter) {
232
236
  });
233
237
  this.socket.on('disconnect', /*#__PURE__*/function () {
234
238
  var _ref2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(reason) {
239
+ var _this2$analyticsHelpe;
235
240
  return _regeneratorRuntime.wrap(function _callee$(_context) {
236
241
  while (1) {
237
242
  switch (_context.prev = _context.next) {
@@ -243,7 +248,18 @@ export var Channel = /*#__PURE__*/function (_Emitter) {
243
248
  });
244
249
  if (reason === 'io server disconnect' && _this2.socket) {
245
250
  // The disconnection was initiated by the server, we need to reconnect manually.
246
- _this2.socket.connect();
251
+ try {
252
+ _this2.socket.connect();
253
+ } catch (error) {
254
+ (_this2$analyticsHelpe = _this2.analyticsHelper) === null || _this2$analyticsHelpe === void 0 ? void 0 : _this2$analyticsHelpe.sendErrorEvent(error, 'Error while reconnecting the channel');
255
+ _this2.emit('error', {
256
+ message: 'Caught error during reconnection.',
257
+ data: {
258
+ status: 500,
259
+ code: 'RECONNECTION_ERROR'
260
+ }
261
+ });
262
+ }
247
263
  }
248
264
  case 4:
249
265
  case "end":
@@ -257,14 +273,26 @@ export var Channel = /*#__PURE__*/function (_Emitter) {
257
273
  };
258
274
  }());
259
275
 
260
- // Socket error, including errors from `packetMiddleware`, `controllers` and `onconnect` and more. Paramter is a plain JSON object.
276
+ // Socket error, including errors from `packetMiddleware`, `controllers` and `onconnect` and more. Parameter is a plain JSON object.
261
277
  this.socket.on('error', function (error) {
262
278
  _this2.emit('error', error);
263
279
  });
264
280
 
265
- // `connect_error`'s paramter type is `Error`.
281
+ // `connect_error`'s parameter type is `Error`.
266
282
  // Ensure the error emit to the provider has the same structure, so we can handle them unified.
267
283
  this.socket.on('connect_error', this.onConnectError);
284
+
285
+ // To trigger reconnection when browser comes back online
286
+ if (!this.network) {
287
+ this.network = new Network({
288
+ onlineCallback: this.onOnlineHandler
289
+ });
290
+ }
291
+
292
+ // Helper class to track reconnection issues, to emit an error if too many failed reconnection attempts happen
293
+ this.reconnectHelper = new ReconnectHelper();
294
+ // Fired upon a reconnection attempt error (from Socket.IO Manager)
295
+ this.socket.io.on('reconnect_error', this.onReconnectError);
268
296
  }
269
297
  }, {
270
298
  key: "fetchCatchup",
@@ -375,7 +403,7 @@ export var Channel = /*#__PURE__*/function (_Emitter) {
375
403
  }
376
404
  };
377
405
  this.emit('error', errorCatchup);
378
- return _context2.abrupt("return", {});
406
+ throw _context2.t19;
379
407
  case 52:
380
408
  case "end":
381
409
  return _context2.stop();
@@ -420,10 +448,15 @@ export var Channel = /*#__PURE__*/function (_Emitter) {
420
448
  }, {
421
449
  key: "disconnect",
422
450
  value: function disconnect() {
451
+ var _this$network;
423
452
  this.unsubscribeAll();
453
+ (_this$network = this.network) === null || _this$network === void 0 ? void 0 : _this$network.destroy();
454
+ this.network = null;
424
455
  if (this.socket) {
456
+ var _this$reconnectHelper3;
425
457
  this.socket.close();
426
458
  this.socket = null;
459
+ (_this$reconnectHelper3 = this.reconnectHelper) === null || _this$reconnectHelper3 === void 0 ? void 0 : _this$reconnectHelper3.destroy();
427
460
  }
428
461
  }
429
462
  }]);
@@ -0,0 +1,45 @@
1
+ import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
2
+ import _createClass from "@babel/runtime/helpers/createClass";
3
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
4
+ export var NetworkStatus;
5
+ (function (NetworkStatus) {
6
+ NetworkStatus["ONLINE"] = "ONLINE";
7
+ NetworkStatus["OFFLINE"] = "OFFLINE";
8
+ })(NetworkStatus || (NetworkStatus = {}));
9
+ var Network = /*#__PURE__*/function () {
10
+ function Network(props) {
11
+ var _this = this;
12
+ _classCallCheck(this, Network);
13
+ _defineProperty(this, "offlineHandler", function () {
14
+ _this.status = NetworkStatus.OFFLINE;
15
+ });
16
+ _defineProperty(this, "onlineHandler", function () {
17
+ _this.status = NetworkStatus.ONLINE;
18
+ if (_this.onlineCallback) {
19
+ _this.onlineCallback();
20
+ }
21
+ });
22
+ if (props !== null && props !== void 0 && props.initialStatus) {
23
+ this.status = props.initialStatus;
24
+ }
25
+ if (props !== null && props !== void 0 && props.onlineCallback) {
26
+ this.onlineCallback = props.onlineCallback;
27
+ }
28
+ window.addEventListener('offline', this.offlineHandler);
29
+ window.addEventListener('online', this.onlineHandler);
30
+ }
31
+ _createClass(Network, [{
32
+ key: "getStatus",
33
+ value: function getStatus() {
34
+ return this.status || null;
35
+ }
36
+ }, {
37
+ key: "destroy",
38
+ value: function destroy() {
39
+ window.removeEventListener('offline', this.offlineHandler);
40
+ window.removeEventListener('online', this.onlineHandler);
41
+ }
42
+ }]);
43
+ return Network;
44
+ }();
45
+ export { Network as default };
@@ -0,0 +1,42 @@
1
+ import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
2
+ import _createClass from "@babel/runtime/helpers/createClass";
3
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
4
+ import { NetworkStatus } from './network';
5
+ import { network } from './singleton';
6
+
7
+ // Calculation for max 8 offline reconnect attempts, with reconnection delay 200ms, randomization factor 0.5, reconnection delay max 128s
8
+ // Min: 800ms, Avg: 51s, Max: 6m
9
+ var FAILED_RECONNECTS_WHILE_OFFLINE_THRESHOLD = 8;
10
+ var ReconnectHelper = /*#__PURE__*/function () {
11
+ function ReconnectHelper() {
12
+ var _this = this;
13
+ _classCallCheck(this, ReconnectHelper);
14
+ _defineProperty(this, "failedReconnectCount", 0);
15
+ _defineProperty(this, "onlineHandler", function () {
16
+ _this.failedReconnectCount = 0;
17
+ });
18
+ window.addEventListener('online', this.onlineHandler);
19
+ }
20
+ _createClass(ReconnectHelper, [{
21
+ key: "countReconnectError",
22
+ value: function countReconnectError() {
23
+ // Only count the reconnection attempts when offline
24
+ if (network.getStatus() === NetworkStatus.OFFLINE) {
25
+ this.failedReconnectCount++;
26
+ }
27
+ }
28
+ }, {
29
+ key: "isLikelyNetworkIssue",
30
+ value: function isLikelyNetworkIssue() {
31
+ var isLikelyNetworkIssue = this.failedReconnectCount >= FAILED_RECONNECTS_WHILE_OFFLINE_THRESHOLD;
32
+ return isLikelyNetworkIssue;
33
+ }
34
+ }, {
35
+ key: "destroy",
36
+ value: function destroy() {
37
+ window.removeEventListener('online', this.onlineHandler);
38
+ }
39
+ }]);
40
+ return ReconnectHelper;
41
+ }();
42
+ export { ReconnectHelper as default };
@@ -0,0 +1,7 @@
1
+ import Network, { NetworkStatus } from './network';
2
+
3
+ // Assume the connection is established at first
4
+ var network = new Network({
5
+ initialStatus: NetworkStatus.ONLINE
6
+ });
7
+ export { network };