@atlaskit/collab-provider 9.7.1 → 9.7.3
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 +12 -0
- package/dist/cjs/channel.js +107 -4
- package/dist/cjs/errors/error-code-mapper.js +7 -1
- package/dist/cjs/errors/error-types.js +5 -0
- package/dist/cjs/feature-flags/__test__/index.unit.js +3 -2
- package/dist/cjs/feature-flags/index.js +4 -2
- package/dist/cjs/helpers/const.js +2 -1
- package/dist/cjs/helpers/socket-message-metrics.js +54 -0
- package/dist/cjs/version-wrapper.js +1 -1
- package/dist/cjs/version.json +1 -1
- package/dist/es2019/channel.js +77 -4
- package/dist/es2019/errors/error-code-mapper.js +7 -1
- package/dist/es2019/errors/error-types.js +5 -0
- package/dist/es2019/feature-flags/__test__/index.unit.js +3 -2
- package/dist/es2019/feature-flags/index.js +4 -2
- package/dist/es2019/helpers/const.js +2 -1
- package/dist/es2019/helpers/socket-message-metrics.js +40 -0
- package/dist/es2019/version-wrapper.js +1 -1
- package/dist/es2019/version.json +1 -1
- package/dist/esm/channel.js +108 -5
- package/dist/esm/errors/error-code-mapper.js +7 -1
- package/dist/esm/errors/error-types.js +5 -0
- package/dist/esm/feature-flags/__test__/index.unit.js +3 -2
- package/dist/esm/feature-flags/index.js +4 -2
- package/dist/esm/helpers/const.js +2 -1
- package/dist/esm/helpers/socket-message-metrics.js +45 -0
- package/dist/esm/version-wrapper.js +1 -1
- package/dist/esm/version.json +1 -1
- package/dist/types/channel.d.ts +13 -2
- package/dist/types/errors/error-types.d.ts +20 -2
- package/dist/types/feature-flags/types.d.ts +1 -0
- package/dist/types/helpers/const.d.ts +12 -2
- package/dist/types/helpers/socket-message-metrics.d.ts +14 -0
- package/dist/types/types.d.ts +9 -0
- package/dist/types-ts4.5/channel.d.ts +13 -2
- package/dist/types-ts4.5/errors/error-types.d.ts +20 -2
- package/dist/types-ts4.5/feature-flags/types.d.ts +1 -0
- package/dist/types-ts4.5/helpers/const.d.ts +12 -2
- package/dist/types-ts4.5/helpers/socket-message-metrics.d.ts +14 -0
- package/dist/types-ts4.5/types.d.ts +9 -0
- package/package.json +2 -2
- package/report.api.md +7 -0
- package/tmp/api-report-tmp.d.ts +7 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @atlaskit/collab-provider
|
|
2
2
|
|
|
3
|
+
## 9.7.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [`04fa8eb5246`](https://bitbucket.org/atlassian/atlassian-frontend/commits/04fa8eb5246) - Added rate limiting options to collab provider
|
|
8
|
+
|
|
9
|
+
## 9.7.2
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [`f9735e0690e`](https://bitbucket.org/atlassian/atlassian-frontend/commits/f9735e0690e) - Using socket.onAnyOutgoing to measure and send message metrics
|
|
14
|
+
|
|
3
15
|
## 9.7.1
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/dist/cjs/channel.js
CHANGED
|
@@ -24,6 +24,11 @@ var _ufo = require("./analytics/ufo");
|
|
|
24
24
|
var _disconnectedReasonMapper = require("./disconnected-reason-mapper");
|
|
25
25
|
var _network = _interopRequireDefault(require("./connectivity/network"));
|
|
26
26
|
var _errorTypes = require("./errors/error-types");
|
|
27
|
+
var _socketMessageMetrics = require("./helpers/socket-message-metrics");
|
|
28
|
+
var _featureFlags = require("./feature-flags");
|
|
29
|
+
function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, 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 normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
|
|
30
|
+
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
|
|
31
|
+
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
|
|
27
32
|
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; }
|
|
28
33
|
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) { (0, _defineProperty2.default)(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; }
|
|
29
34
|
function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = (0, _getPrototypeOf2.default)(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = (0, _getPrototypeOf2.default)(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return (0, _possibleConstructorReturn2.default)(this, result); }; }
|
|
@@ -36,11 +41,19 @@ var Channel = /*#__PURE__*/function (_Emitter) {
|
|
|
36
41
|
var _this;
|
|
37
42
|
(0, _classCallCheck2.default)(this, Channel);
|
|
38
43
|
_this = _super.call(this);
|
|
44
|
+
(0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "RATE_LIMIT_TYPE_NONE", 0);
|
|
45
|
+
(0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "RATE_LIMIT_TYPE_SOFT", 1);
|
|
46
|
+
(0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "RATE_LIMIT_TYPE_HARD", 2);
|
|
39
47
|
(0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "connected", false);
|
|
40
48
|
(0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "socket", null);
|
|
41
49
|
(0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "reconnectHelper", null);
|
|
42
50
|
(0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "initialized", false);
|
|
43
51
|
(0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "network", null);
|
|
52
|
+
(0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "rateLimitWindowDurationMs", 60000);
|
|
53
|
+
(0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "rateLimitWindowStartMs", 0);
|
|
54
|
+
(0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "stepCounter", 0);
|
|
55
|
+
(0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "stepSizeCounter", 0);
|
|
56
|
+
(0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "maxStepSize", 0);
|
|
44
57
|
// read-only getters used for tests
|
|
45
58
|
(0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "getInitialized", function () {
|
|
46
59
|
return _this.initialized;
|
|
@@ -70,7 +83,7 @@ var Channel = /*#__PURE__*/function (_Emitter) {
|
|
|
70
83
|
reason: data.reason,
|
|
71
84
|
// Potentially incorrect when value of token changes between connecting and connect.
|
|
72
85
|
// See: https://bitbucket.org/atlassian/%7Bc8e2f021-38d2-46d0-9b7a-b3f7b428f724%7D/pull-requests/29905#comment-375308874
|
|
73
|
-
usedCachedToken: _this.token
|
|
86
|
+
usedCachedToken: !!_this.token
|
|
74
87
|
});
|
|
75
88
|
_this.unsetToken();
|
|
76
89
|
});
|
|
@@ -81,7 +94,7 @@ var Channel = /*#__PURE__*/function (_Emitter) {
|
|
|
81
94
|
latency: measure === null || measure === void 0 ? void 0 : measure.duration,
|
|
82
95
|
// Potentially incorrect when value of token changes between connecting and connect.
|
|
83
96
|
// See: https://bitbucket.org/atlassian/%7Bc8e2f021-38d2-46d0-9b7a-b3f7b428f724%7D/pull-requests/29905#comment-375308874
|
|
84
|
-
usedCachedToken: _this.token
|
|
97
|
+
usedCachedToken: !!_this.token
|
|
85
98
|
});
|
|
86
99
|
(_this$analyticsHelper3 = _this.analyticsHelper) === null || _this$analyticsHelper3 === void 0 ? void 0 : _this$analyticsHelper3.sendErrorEvent(error, 'Error while establishing connection');
|
|
87
100
|
// If error received with `data`, it means the connection is rejected
|
|
@@ -125,6 +138,9 @@ var Channel = /*#__PURE__*/function (_Emitter) {
|
|
|
125
138
|
});
|
|
126
139
|
(0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "onConnect", function () {
|
|
127
140
|
var _this$analyticsHelper5;
|
|
141
|
+
if ((0, _featureFlags.getCollabProviderFeatureFlag)('socketMessageMetricsFF', _this.config.featureFlags) && _this.socketMessageMetrics) {
|
|
142
|
+
_this.socketMessageMetrics.setupSocketMessageMetrics();
|
|
143
|
+
}
|
|
128
144
|
_this.connected = true;
|
|
129
145
|
logger('Connected.', _this.socket.id);
|
|
130
146
|
var measure = (0, _performance.stopMeasure)(_performance.MEASURE_NAME.SOCKET_CONNECT, _this.analyticsHelper);
|
|
@@ -132,7 +148,7 @@ var Channel = /*#__PURE__*/function (_Emitter) {
|
|
|
132
148
|
latency: measure === null || measure === void 0 ? void 0 : measure.duration,
|
|
133
149
|
// Potentially incorrect when value of token changes between connecting and connect.
|
|
134
150
|
// See: https://bitbucket.org/atlassian/%7Bc8e2f021-38d2-46d0-9b7a-b3f7b428f724%7D/pull-requests/29905#comment-375308874
|
|
135
|
-
usedCachedToken: _this.token
|
|
151
|
+
usedCachedToken: !!_this.token
|
|
136
152
|
});
|
|
137
153
|
_this.emit('connected', {
|
|
138
154
|
sid: _this.socket.id,
|
|
@@ -474,6 +490,9 @@ var Channel = /*#__PURE__*/function (_Emitter) {
|
|
|
474
490
|
};
|
|
475
491
|
}
|
|
476
492
|
this.socket = createSocket("".concat(url, "/session/").concat(documentAri), auth, this.config.productInfo);
|
|
493
|
+
if (this.socket && this.analyticsHelper) {
|
|
494
|
+
this.socketMessageMetrics = new _socketMessageMetrics.SocketMessageMetrics(this.socket, this.analyticsHelper);
|
|
495
|
+
}
|
|
477
496
|
|
|
478
497
|
// Due to https://github.com/socketio/socket.io-client/issues/1473,
|
|
479
498
|
// reconnect no longer fired on the socket.
|
|
@@ -525,6 +544,9 @@ var Channel = /*#__PURE__*/function (_Emitter) {
|
|
|
525
544
|
return _regenerator.default.wrap(function _callee3$(_context3) {
|
|
526
545
|
while (1) switch (_context3.prev = _context3.next) {
|
|
527
546
|
case 0:
|
|
547
|
+
if ((0, _featureFlags.getCollabProviderFeatureFlag)('socketMessageMetricsFF', _this2.config.featureFlags) && _this2.socketMessageMetrics) {
|
|
548
|
+
_this2.socketMessageMetrics.closeSocketMessageMetrics();
|
|
549
|
+
}
|
|
528
550
|
_this2.connected = false;
|
|
529
551
|
logger("disconnect reason: ".concat(reason));
|
|
530
552
|
_this2.emit('disconnect', {
|
|
@@ -546,7 +568,7 @@ var Channel = /*#__PURE__*/function (_Emitter) {
|
|
|
546
568
|
_this2.emit('error', reconnectionError);
|
|
547
569
|
}
|
|
548
570
|
}
|
|
549
|
-
case
|
|
571
|
+
case 5:
|
|
550
572
|
case "end":
|
|
551
573
|
return _context3.stop();
|
|
552
574
|
}
|
|
@@ -566,6 +588,12 @@ var Channel = /*#__PURE__*/function (_Emitter) {
|
|
|
566
588
|
// Ensure the error emit to the provider has the same structure, so we can handle them unified.
|
|
567
589
|
this.socket.on('connect_error', this.onConnectError);
|
|
568
590
|
this.socket.on('permission:invalidateToken', this.handlePermissionInvalidateToken);
|
|
591
|
+
this.socket.onAnyOutgoing(function (event) {
|
|
592
|
+
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
|
|
593
|
+
args[_key - 1] = arguments[_key];
|
|
594
|
+
}
|
|
595
|
+
return _this2.onAnyOutgoingHandler(Date.now(), args);
|
|
596
|
+
});
|
|
569
597
|
|
|
570
598
|
// To trigger reconnection when browser comes back online
|
|
571
599
|
if (!this.network) {
|
|
@@ -579,6 +607,81 @@ var Channel = /*#__PURE__*/function (_Emitter) {
|
|
|
579
607
|
// Fired upon a reconnection attempt error (from Socket.IO Manager)
|
|
580
608
|
this.socket.io.on('reconnect_error', this.onReconnectError);
|
|
581
609
|
}
|
|
610
|
+
}, {
|
|
611
|
+
key: "onAnyOutgoingHandler",
|
|
612
|
+
value: function onAnyOutgoingHandler(currentTimeMs, args) {
|
|
613
|
+
var rateLimitType = this.config.rateLimitType || this.RATE_LIMIT_TYPE_NONE;
|
|
614
|
+
if (rateLimitType === this.RATE_LIMIT_TYPE_NONE) {
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
var stepLimit = this.config.rateLimitStepCount || 0;
|
|
618
|
+
var stepSizeLimit = this.config.rateLimitTotalStepSize || 0;
|
|
619
|
+
var maxStepSizeLimit = this.config.rateLimitMaxStepSize || 0;
|
|
620
|
+
var _iterator = _createForOfIteratorHelper(args),
|
|
621
|
+
_step;
|
|
622
|
+
try {
|
|
623
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
624
|
+
var arg = _step.value;
|
|
625
|
+
if (arg.type === 'steps:commit') {
|
|
626
|
+
if (currentTimeMs - this.rateLimitWindowStartMs > this.rateLimitWindowDurationMs) {
|
|
627
|
+
// Start a new window
|
|
628
|
+
this.rateLimitWindowStartMs = currentTimeMs;
|
|
629
|
+
this.stepCounter = 0;
|
|
630
|
+
this.stepSizeCounter = 0;
|
|
631
|
+
this.maxStepSize = 0;
|
|
632
|
+
}
|
|
633
|
+
this.stepCounter += arg.steps.length;
|
|
634
|
+
var _iterator2 = _createForOfIteratorHelper(arg.steps),
|
|
635
|
+
_step2;
|
|
636
|
+
try {
|
|
637
|
+
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
|
|
638
|
+
var step = _step2.value;
|
|
639
|
+
var stepSize = JSON.stringify(step).length;
|
|
640
|
+
this.stepSizeCounter += stepSize;
|
|
641
|
+
if (stepSize > this.maxStepSize) {
|
|
642
|
+
this.maxStepSize = stepSize;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
} catch (err) {
|
|
646
|
+
_iterator2.e(err);
|
|
647
|
+
} finally {
|
|
648
|
+
_iterator2.f();
|
|
649
|
+
}
|
|
650
|
+
if (this.isLimitExceeded(stepLimit, stepSizeLimit, maxStepSizeLimit)) {
|
|
651
|
+
var rateLimitError = {
|
|
652
|
+
message: 'Rate limited',
|
|
653
|
+
data: {
|
|
654
|
+
code: _errorTypes.NCS_ERROR_CODE.RATE_LIMIT_ERROR,
|
|
655
|
+
status: 500,
|
|
656
|
+
meta: {
|
|
657
|
+
rateLimitType: rateLimitType,
|
|
658
|
+
maxStepSize: this.maxStepSize,
|
|
659
|
+
stepSizeCounter: this.stepSizeCounter,
|
|
660
|
+
stepCounter: this.stepCounter
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
};
|
|
664
|
+
if (rateLimitType === this.RATE_LIMIT_TYPE_HARD) {
|
|
665
|
+
this.emit('error', rateLimitError);
|
|
666
|
+
throw new Error();
|
|
667
|
+
} else if (rateLimitType === this.RATE_LIMIT_TYPE_SOFT) {
|
|
668
|
+
var _this$analyticsHelper7;
|
|
669
|
+
(_this$analyticsHelper7 = this.analyticsHelper) === null || _this$analyticsHelper7 === void 0 ? void 0 : _this$analyticsHelper7.sendErrorEvent(rateLimitError, 'Rate limited');
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
} catch (err) {
|
|
675
|
+
_iterator.e(err);
|
|
676
|
+
} finally {
|
|
677
|
+
_iterator.f();
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
}, {
|
|
681
|
+
key: "isLimitExceeded",
|
|
682
|
+
value: function isLimitExceeded(stepLimit, stepSizeLimit, maxStepSizeLimit) {
|
|
683
|
+
return stepLimit > 0 && this.stepCounter > stepLimit || stepSizeLimit > 0 && this.stepSizeCounter > stepSizeLimit || maxStepSizeLimit > 0 && this.maxStepSize > maxStepSizeLimit;
|
|
684
|
+
}
|
|
582
685
|
}, {
|
|
583
686
|
key: "disconnect",
|
|
584
687
|
value: function disconnect() {
|
|
@@ -103,7 +103,7 @@ var errorCodeMapper = function errorCodeMapper(error) {
|
|
|
103
103
|
return {
|
|
104
104
|
code: _collab.PROVIDER_ERROR_CODE.INTERNAL_SERVICE_ERROR,
|
|
105
105
|
message: 'Collab Provider experienced an unrecoverable error',
|
|
106
|
-
recoverable:
|
|
106
|
+
recoverable: true,
|
|
107
107
|
reason: (_error$data3 = error.data) === null || _error$data3 === void 0 ? void 0 : _error$data3.code,
|
|
108
108
|
status: 500
|
|
109
109
|
};
|
|
@@ -115,6 +115,12 @@ var errorCodeMapper = function errorCodeMapper(error) {
|
|
|
115
115
|
reason: (_error$data4 = error.data) === null || _error$data4 === void 0 ? void 0 : _error$data4.code,
|
|
116
116
|
status: 500
|
|
117
117
|
};
|
|
118
|
+
case _errorTypes.NCS_ERROR_CODE.RATE_LIMIT_ERROR:
|
|
119
|
+
return {
|
|
120
|
+
code: _collab.PROVIDER_ERROR_CODE.FAIL_TO_SAVE,
|
|
121
|
+
message: 'Document rate limit',
|
|
122
|
+
recoverable: false
|
|
123
|
+
};
|
|
118
124
|
default:
|
|
119
125
|
return;
|
|
120
126
|
}
|
|
@@ -46,6 +46,7 @@ var NCS_ERROR_CODE = /*#__PURE__*/function (NCS_ERROR_CODE) {
|
|
|
46
46
|
NCS_ERROR_CODE["INVALID_ACTIVATION_ID"] = "INVALID_ACTIVATION_ID";
|
|
47
47
|
NCS_ERROR_CODE["INVALID_DOCUMENT_ARI"] = "INVALID_DOCUMENT_ARI";
|
|
48
48
|
NCS_ERROR_CODE["INVALID_CLOUD_ID"] = "INVALID_CLOUD_ID";
|
|
49
|
+
NCS_ERROR_CODE["RATE_LIMIT_ERROR"] = "RATE_LIMIT_ERROR";
|
|
49
50
|
return NCS_ERROR_CODE;
|
|
50
51
|
}({}); // TODO: Import emitted error codes from NCS
|
|
51
52
|
// NCS Errors
|
|
@@ -60,6 +61,10 @@ var NCS_ERROR_CODE = /*#__PURE__*/function (NCS_ERROR_CODE) {
|
|
|
60
61
|
* When we try to apply state updates to the editor, if that fails to apply the user can enter an invalid state where no
|
|
61
62
|
* changes can be saved to NCS.
|
|
62
63
|
*/
|
|
64
|
+
/**
|
|
65
|
+
* The client is trying to send too many messages or messages that are too large. This not expected to be a standard
|
|
66
|
+
* operating condition and should only ever indicate a frontend bug.
|
|
67
|
+
*/
|
|
63
68
|
/**
|
|
64
69
|
* A union of all possible internal errors, that are mapped to another error if being emitted to the editor.
|
|
65
70
|
*/
|
|
@@ -4,9 +4,10 @@ var _index = require("../index");
|
|
|
4
4
|
describe('Feature flags', function () {
|
|
5
5
|
it('getProductSpecificFeatureFlags', function () {
|
|
6
6
|
var result = (0, _index.getProductSpecificFeatureFlags)({
|
|
7
|
-
testFF: true
|
|
7
|
+
testFF: true,
|
|
8
|
+
socketMessageMetricsFF: true
|
|
8
9
|
}, 'confluence');
|
|
9
|
-
expect(result).toEqual(['confluence.fe.collab.provider.testFF']);
|
|
10
|
+
expect(result).toEqual(['confluence.fe.collab.provider.testFF', 'confluence.fe.collab.provider.socketMessageMetricsFF']);
|
|
10
11
|
});
|
|
11
12
|
it('getCollabProviderFeatureFlag return true', function () {
|
|
12
13
|
var result = (0, _index.getCollabProviderFeatureFlag)('testFF', {
|
|
@@ -8,7 +8,8 @@ exports.getCollabProviderFeatureFlag = getCollabProviderFeatureFlag;
|
|
|
8
8
|
exports.getProductSpecificFeatureFlags = void 0;
|
|
9
9
|
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
|
|
10
10
|
var defaultNCSFeatureFlags = {
|
|
11
|
-
testFF: false
|
|
11
|
+
testFF: false,
|
|
12
|
+
socketMessageMetricsFF: false
|
|
12
13
|
};
|
|
13
14
|
|
|
14
15
|
/**
|
|
@@ -16,7 +17,8 @@ var defaultNCSFeatureFlags = {
|
|
|
16
17
|
*/
|
|
17
18
|
var productKeys = {
|
|
18
19
|
confluence: {
|
|
19
|
-
testFF: 'confluence.fe.collab.provider.testFF'
|
|
20
|
+
testFF: 'confluence.fe.collab.provider.testFF',
|
|
21
|
+
socketMessageMetricsFF: 'confluence.fe.collab.provider.socketMessageMetricsFF'
|
|
20
22
|
}
|
|
21
23
|
};
|
|
22
24
|
var filterFeatureFlagNames = function filterFeatureFlagNames(flags) {
|
|
@@ -19,8 +19,9 @@ var EVENT_ACTION = /*#__PURE__*/function (EVENT_ACTION) {
|
|
|
19
19
|
EVENT_ACTION["SEND_STEPS_RETRY"] = "sendStepsRetry";
|
|
20
20
|
EVENT_ACTION["CATCHUP_AFTER_MAX_SEND_STEPS_RETRY"] = "catchupAfterMaxSendStepsRetry";
|
|
21
21
|
EVENT_ACTION["DROPPED_STEPS"] = "droppedStepInCatchup";
|
|
22
|
+
EVENT_ACTION["WEBSOCKET_MESSAGE_VOLUME_METRIC"] = "websocketMessageVolumeMetric";
|
|
22
23
|
return EVENT_ACTION;
|
|
23
|
-
}({}); // https://data-portal.internal.atlassian.com/analytics/registry/
|
|
24
|
+
}({}); // https://data-portal.internal.atlassian.com/analytics/registry/53596
|
|
24
25
|
exports.EVENT_ACTION = EVENT_ACTION;
|
|
25
26
|
var EVENT_STATUS = /*#__PURE__*/function (EVENT_STATUS) {
|
|
26
27
|
EVENT_STATUS["SUCCESS"] = "SUCCESS";
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports.WEBSOCKET_MESSAGE_VOLUME_METRIC_SEND_INTERVAL_MS = exports.SocketMessageMetrics = void 0;
|
|
8
|
+
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
|
|
9
|
+
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
|
|
10
|
+
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
11
|
+
var _const = require("./const");
|
|
12
|
+
var _utils = require("./utils");
|
|
13
|
+
var logger = (0, _utils.createLogger)('SocketMessageMetrics', 'green');
|
|
14
|
+
var WEBSOCKET_MESSAGE_VOLUME_METRIC_SEND_INTERVAL_MS = 60000;
|
|
15
|
+
exports.WEBSOCKET_MESSAGE_VOLUME_METRIC_SEND_INTERVAL_MS = WEBSOCKET_MESSAGE_VOLUME_METRIC_SEND_INTERVAL_MS;
|
|
16
|
+
var SocketMessageMetrics = /*#__PURE__*/(0, _createClass2.default)(function SocketMessageMetrics(socket, analyticsHelper) {
|
|
17
|
+
var _this = this;
|
|
18
|
+
(0, _classCallCheck2.default)(this, SocketMessageMetrics);
|
|
19
|
+
(0, _defineProperty2.default)(this, "messageCount", 0);
|
|
20
|
+
(0, _defineProperty2.default)(this, "totalMessageSize", 0);
|
|
21
|
+
(0, _defineProperty2.default)(this, "metricsIntervalID", undefined);
|
|
22
|
+
(0, _defineProperty2.default)(this, "socketMessageMetricsListener", function (event) {
|
|
23
|
+
_this.messageCount++;
|
|
24
|
+
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
|
|
25
|
+
args[_key - 1] = arguments[_key];
|
|
26
|
+
}
|
|
27
|
+
_this.totalMessageSize += Buffer.byteLength(JSON.stringify(args), 'utf8');
|
|
28
|
+
});
|
|
29
|
+
(0, _defineProperty2.default)(this, "setupSocketMessageMetrics", function () {
|
|
30
|
+
if (_this.metricsIntervalID !== undefined) {
|
|
31
|
+
logger('calling setupSocketMessageMetrics function with metricsIntervalID that is not undefined');
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
_this.socket.onAnyOutgoing(_this.socketMessageMetricsListener);
|
|
35
|
+
|
|
36
|
+
// send metrics every 60 seconds
|
|
37
|
+
_this.metricsIntervalID = window.setInterval(function () {
|
|
38
|
+
_this.analyticsHelper.sendActionEvent(_const.EVENT_ACTION.WEBSOCKET_MESSAGE_VOLUME_METRIC, _const.EVENT_STATUS.INFO, {
|
|
39
|
+
messageCount: _this.messageCount,
|
|
40
|
+
totalMessageSize: _this.totalMessageSize
|
|
41
|
+
});
|
|
42
|
+
_this.messageCount = 0;
|
|
43
|
+
_this.totalMessageSize = 0;
|
|
44
|
+
}, WEBSOCKET_MESSAGE_VOLUME_METRIC_SEND_INTERVAL_MS);
|
|
45
|
+
});
|
|
46
|
+
(0, _defineProperty2.default)(this, "closeSocketMessageMetrics", function () {
|
|
47
|
+
clearInterval(_this.metricsIntervalID);
|
|
48
|
+
_this.metricsIntervalID = undefined;
|
|
49
|
+
_this.socket.offAnyOutgoing(_this.socketMessageMetricsListener);
|
|
50
|
+
});
|
|
51
|
+
this.socket = socket;
|
|
52
|
+
this.analyticsHelper = analyticsHelper;
|
|
53
|
+
});
|
|
54
|
+
exports.SocketMessageMetrics = SocketMessageMetrics;
|
|
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
exports.version = exports.nextMajorVersion = exports.name = void 0;
|
|
7
7
|
var name = "@atlaskit/collab-provider";
|
|
8
8
|
exports.name = name;
|
|
9
|
-
var version = "9.7.
|
|
9
|
+
var version = "9.7.3";
|
|
10
10
|
exports.version = version;
|
|
11
11
|
var nextMajorVersion = function nextMajorVersion() {
|
|
12
12
|
return [Number(version.split('.')[0]) + 1, 0, 0].join('.');
|
package/dist/cjs/version.json
CHANGED
package/dist/es2019/channel.js
CHANGED
|
@@ -8,16 +8,26 @@ import ReconnectHelper from './connectivity/reconnect-helper';
|
|
|
8
8
|
import { createDocInitExp } from './analytics/ufo';
|
|
9
9
|
import { socketIOReasons } from './disconnected-reason-mapper';
|
|
10
10
|
import Network from './connectivity/network';
|
|
11
|
-
import { NotConnectedError, NotInitializedError, INTERNAL_ERROR_CODE } from './errors/error-types';
|
|
11
|
+
import { NotConnectedError, NotInitializedError, INTERNAL_ERROR_CODE, NCS_ERROR_CODE } from './errors/error-types';
|
|
12
|
+
import { SocketMessageMetrics } from './helpers/socket-message-metrics';
|
|
13
|
+
import { getCollabProviderFeatureFlag } from './feature-flags';
|
|
12
14
|
const logger = createLogger('Channel', 'green');
|
|
13
15
|
export class Channel extends Emitter {
|
|
14
16
|
constructor(config, analyticsHelper) {
|
|
15
17
|
super();
|
|
18
|
+
_defineProperty(this, "RATE_LIMIT_TYPE_NONE", 0);
|
|
19
|
+
_defineProperty(this, "RATE_LIMIT_TYPE_SOFT", 1);
|
|
20
|
+
_defineProperty(this, "RATE_LIMIT_TYPE_HARD", 2);
|
|
16
21
|
_defineProperty(this, "connected", false);
|
|
17
22
|
_defineProperty(this, "socket", null);
|
|
18
23
|
_defineProperty(this, "reconnectHelper", null);
|
|
19
24
|
_defineProperty(this, "initialized", false);
|
|
20
25
|
_defineProperty(this, "network", null);
|
|
26
|
+
_defineProperty(this, "rateLimitWindowDurationMs", 60000);
|
|
27
|
+
_defineProperty(this, "rateLimitWindowStartMs", 0);
|
|
28
|
+
_defineProperty(this, "stepCounter", 0);
|
|
29
|
+
_defineProperty(this, "stepSizeCounter", 0);
|
|
30
|
+
_defineProperty(this, "maxStepSize", 0);
|
|
21
31
|
// read-only getters used for tests
|
|
22
32
|
_defineProperty(this, "getInitialized", () => this.initialized);
|
|
23
33
|
_defineProperty(this, "getConnected", () => this.connected);
|
|
@@ -37,7 +47,7 @@ export class Channel extends Emitter {
|
|
|
37
47
|
reason: data.reason,
|
|
38
48
|
// Potentially incorrect when value of token changes between connecting and connect.
|
|
39
49
|
// See: https://bitbucket.org/atlassian/%7Bc8e2f021-38d2-46d0-9b7a-b3f7b428f724%7D/pull-requests/29905#comment-375308874
|
|
40
|
-
usedCachedToken: this.token
|
|
50
|
+
usedCachedToken: !!this.token
|
|
41
51
|
});
|
|
42
52
|
this.unsetToken();
|
|
43
53
|
});
|
|
@@ -48,7 +58,7 @@ export class Channel extends Emitter {
|
|
|
48
58
|
latency: measure === null || measure === void 0 ? void 0 : measure.duration,
|
|
49
59
|
// Potentially incorrect when value of token changes between connecting and connect.
|
|
50
60
|
// See: https://bitbucket.org/atlassian/%7Bc8e2f021-38d2-46d0-9b7a-b3f7b428f724%7D/pull-requests/29905#comment-375308874
|
|
51
|
-
usedCachedToken: this.token
|
|
61
|
+
usedCachedToken: !!this.token
|
|
52
62
|
});
|
|
53
63
|
(_this$analyticsHelper3 = this.analyticsHelper) === null || _this$analyticsHelper3 === void 0 ? void 0 : _this$analyticsHelper3.sendErrorEvent(error, 'Error while establishing connection');
|
|
54
64
|
// If error received with `data`, it means the connection is rejected
|
|
@@ -93,6 +103,9 @@ export class Channel extends Emitter {
|
|
|
93
103
|
});
|
|
94
104
|
_defineProperty(this, "onConnect", () => {
|
|
95
105
|
var _this$analyticsHelper5;
|
|
106
|
+
if (getCollabProviderFeatureFlag('socketMessageMetricsFF', this.config.featureFlags) && this.socketMessageMetrics) {
|
|
107
|
+
this.socketMessageMetrics.setupSocketMessageMetrics();
|
|
108
|
+
}
|
|
96
109
|
this.connected = true;
|
|
97
110
|
logger('Connected.', this.socket.id);
|
|
98
111
|
const measure = stopMeasure(MEASURE_NAME.SOCKET_CONNECT, this.analyticsHelper);
|
|
@@ -100,7 +113,7 @@ export class Channel extends Emitter {
|
|
|
100
113
|
latency: measure === null || measure === void 0 ? void 0 : measure.duration,
|
|
101
114
|
// Potentially incorrect when value of token changes between connecting and connect.
|
|
102
115
|
// See: https://bitbucket.org/atlassian/%7Bc8e2f021-38d2-46d0-9b7a-b3f7b428f724%7D/pull-requests/29905#comment-375308874
|
|
103
|
-
usedCachedToken: this.token
|
|
116
|
+
usedCachedToken: !!this.token
|
|
104
117
|
});
|
|
105
118
|
this.emit('connected', {
|
|
106
119
|
sid: this.socket.id,
|
|
@@ -350,6 +363,9 @@ export class Channel extends Emitter {
|
|
|
350
363
|
};
|
|
351
364
|
}
|
|
352
365
|
this.socket = createSocket(`${url}/session/${documentAri}`, auth, this.config.productInfo);
|
|
366
|
+
if (this.socket && this.analyticsHelper) {
|
|
367
|
+
this.socketMessageMetrics = new SocketMessageMetrics(this.socket, this.analyticsHelper);
|
|
368
|
+
}
|
|
353
369
|
|
|
354
370
|
// Due to https://github.com/socketio/socket.io-client/issues/1473,
|
|
355
371
|
// reconnect no longer fired on the socket.
|
|
@@ -400,6 +416,9 @@ export class Channel extends Emitter {
|
|
|
400
416
|
this.emit('status', data);
|
|
401
417
|
});
|
|
402
418
|
this.socket.on('disconnect', async reason => {
|
|
419
|
+
if (getCollabProviderFeatureFlag('socketMessageMetricsFF', this.config.featureFlags) && this.socketMessageMetrics) {
|
|
420
|
+
this.socketMessageMetrics.closeSocketMessageMetrics();
|
|
421
|
+
}
|
|
403
422
|
this.connected = false;
|
|
404
423
|
logger(`disconnect reason: ${reason}`);
|
|
405
424
|
this.emit('disconnect', {
|
|
@@ -433,6 +452,7 @@ export class Channel extends Emitter {
|
|
|
433
452
|
// Ensure the error emit to the provider has the same structure, so we can handle them unified.
|
|
434
453
|
this.socket.on('connect_error', this.onConnectError);
|
|
435
454
|
this.socket.on('permission:invalidateToken', this.handlePermissionInvalidateToken);
|
|
455
|
+
this.socket.onAnyOutgoing((event, ...args) => this.onAnyOutgoingHandler(Date.now(), args));
|
|
436
456
|
|
|
437
457
|
// To trigger reconnection when browser comes back online
|
|
438
458
|
if (!this.network) {
|
|
@@ -446,6 +466,59 @@ export class Channel extends Emitter {
|
|
|
446
466
|
// Fired upon a reconnection attempt error (from Socket.IO Manager)
|
|
447
467
|
this.socket.io.on('reconnect_error', this.onReconnectError);
|
|
448
468
|
}
|
|
469
|
+
onAnyOutgoingHandler(currentTimeMs, args) {
|
|
470
|
+
const rateLimitType = this.config.rateLimitType || this.RATE_LIMIT_TYPE_NONE;
|
|
471
|
+
if (rateLimitType === this.RATE_LIMIT_TYPE_NONE) {
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
const stepLimit = this.config.rateLimitStepCount || 0;
|
|
475
|
+
const stepSizeLimit = this.config.rateLimitTotalStepSize || 0;
|
|
476
|
+
const maxStepSizeLimit = this.config.rateLimitMaxStepSize || 0;
|
|
477
|
+
for (const arg of args) {
|
|
478
|
+
if (arg.type === 'steps:commit') {
|
|
479
|
+
if (currentTimeMs - this.rateLimitWindowStartMs > this.rateLimitWindowDurationMs) {
|
|
480
|
+
// Start a new window
|
|
481
|
+
this.rateLimitWindowStartMs = currentTimeMs;
|
|
482
|
+
this.stepCounter = 0;
|
|
483
|
+
this.stepSizeCounter = 0;
|
|
484
|
+
this.maxStepSize = 0;
|
|
485
|
+
}
|
|
486
|
+
this.stepCounter += arg.steps.length;
|
|
487
|
+
for (const step of arg.steps) {
|
|
488
|
+
const stepSize = JSON.stringify(step).length;
|
|
489
|
+
this.stepSizeCounter += stepSize;
|
|
490
|
+
if (stepSize > this.maxStepSize) {
|
|
491
|
+
this.maxStepSize = stepSize;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
if (this.isLimitExceeded(stepLimit, stepSizeLimit, maxStepSizeLimit)) {
|
|
495
|
+
const rateLimitError = {
|
|
496
|
+
message: 'Rate limited',
|
|
497
|
+
data: {
|
|
498
|
+
code: NCS_ERROR_CODE.RATE_LIMIT_ERROR,
|
|
499
|
+
status: 500,
|
|
500
|
+
meta: {
|
|
501
|
+
rateLimitType,
|
|
502
|
+
maxStepSize: this.maxStepSize,
|
|
503
|
+
stepSizeCounter: this.stepSizeCounter,
|
|
504
|
+
stepCounter: this.stepCounter
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
if (rateLimitType === this.RATE_LIMIT_TYPE_HARD) {
|
|
509
|
+
this.emit('error', rateLimitError);
|
|
510
|
+
throw new Error();
|
|
511
|
+
} else if (rateLimitType === this.RATE_LIMIT_TYPE_SOFT) {
|
|
512
|
+
var _this$analyticsHelper8;
|
|
513
|
+
(_this$analyticsHelper8 = this.analyticsHelper) === null || _this$analyticsHelper8 === void 0 ? void 0 : _this$analyticsHelper8.sendErrorEvent(rateLimitError, 'Rate limited');
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
isLimitExceeded(stepLimit, stepSizeLimit, maxStepSizeLimit) {
|
|
520
|
+
return stepLimit > 0 && this.stepCounter > stepLimit || stepSizeLimit > 0 && this.stepSizeCounter > stepSizeLimit || maxStepSizeLimit > 0 && this.maxStepSize > maxStepSizeLimit;
|
|
521
|
+
}
|
|
449
522
|
disconnect() {
|
|
450
523
|
var _this$network;
|
|
451
524
|
this.unsubscribeAll();
|
|
@@ -99,7 +99,7 @@ export const errorCodeMapper = error => {
|
|
|
99
99
|
return {
|
|
100
100
|
code: PROVIDER_ERROR_CODE.INTERNAL_SERVICE_ERROR,
|
|
101
101
|
message: 'Collab Provider experienced an unrecoverable error',
|
|
102
|
-
recoverable:
|
|
102
|
+
recoverable: true,
|
|
103
103
|
reason: (_error$data3 = error.data) === null || _error$data3 === void 0 ? void 0 : _error$data3.code,
|
|
104
104
|
status: 500
|
|
105
105
|
};
|
|
@@ -111,6 +111,12 @@ export const errorCodeMapper = error => {
|
|
|
111
111
|
reason: (_error$data4 = error.data) === null || _error$data4 === void 0 ? void 0 : _error$data4.code,
|
|
112
112
|
status: 500
|
|
113
113
|
};
|
|
114
|
+
case NCS_ERROR_CODE.RATE_LIMIT_ERROR:
|
|
115
|
+
return {
|
|
116
|
+
code: PROVIDER_ERROR_CODE.FAIL_TO_SAVE,
|
|
117
|
+
message: 'Document rate limit',
|
|
118
|
+
recoverable: false
|
|
119
|
+
};
|
|
114
120
|
default:
|
|
115
121
|
return;
|
|
116
122
|
}
|
|
@@ -32,6 +32,7 @@ export let NCS_ERROR_CODE = /*#__PURE__*/function (NCS_ERROR_CODE) {
|
|
|
32
32
|
NCS_ERROR_CODE["INVALID_ACTIVATION_ID"] = "INVALID_ACTIVATION_ID";
|
|
33
33
|
NCS_ERROR_CODE["INVALID_DOCUMENT_ARI"] = "INVALID_DOCUMENT_ARI";
|
|
34
34
|
NCS_ERROR_CODE["INVALID_CLOUD_ID"] = "INVALID_CLOUD_ID";
|
|
35
|
+
NCS_ERROR_CODE["RATE_LIMIT_ERROR"] = "RATE_LIMIT_ERROR";
|
|
35
36
|
return NCS_ERROR_CODE;
|
|
36
37
|
}({});
|
|
37
38
|
|
|
@@ -49,6 +50,10 @@ export let NCS_ERROR_CODE = /*#__PURE__*/function (NCS_ERROR_CODE) {
|
|
|
49
50
|
* When we try to apply state updates to the editor, if that fails to apply the user can enter an invalid state where no
|
|
50
51
|
* changes can be saved to NCS.
|
|
51
52
|
*/
|
|
53
|
+
/**
|
|
54
|
+
* The client is trying to send too many messages or messages that are too large. This not expected to be a standard
|
|
55
|
+
* operating condition and should only ever indicate a frontend bug.
|
|
56
|
+
*/
|
|
52
57
|
/**
|
|
53
58
|
* A union of all possible internal errors, that are mapped to another error if being emitted to the editor.
|
|
54
59
|
*/
|
|
@@ -2,9 +2,10 @@ import { getProductSpecificFeatureFlags, getCollabProviderFeatureFlag } from '..
|
|
|
2
2
|
describe('Feature flags', () => {
|
|
3
3
|
it('getProductSpecificFeatureFlags', () => {
|
|
4
4
|
const result = getProductSpecificFeatureFlags({
|
|
5
|
-
testFF: true
|
|
5
|
+
testFF: true,
|
|
6
|
+
socketMessageMetricsFF: true
|
|
6
7
|
}, 'confluence');
|
|
7
|
-
expect(result).toEqual(['confluence.fe.collab.provider.testFF']);
|
|
8
|
+
expect(result).toEqual(['confluence.fe.collab.provider.testFF', 'confluence.fe.collab.provider.socketMessageMetricsFF']);
|
|
8
9
|
});
|
|
9
10
|
it('getCollabProviderFeatureFlag return true', () => {
|
|
10
11
|
const result = getCollabProviderFeatureFlag('testFF', {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const defaultNCSFeatureFlags = {
|
|
2
|
-
testFF: false
|
|
2
|
+
testFF: false,
|
|
3
|
+
socketMessageMetricsFF: false
|
|
3
4
|
};
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -7,7 +8,8 @@ const defaultNCSFeatureFlags = {
|
|
|
7
8
|
*/
|
|
8
9
|
const productKeys = {
|
|
9
10
|
confluence: {
|
|
10
|
-
testFF: 'confluence.fe.collab.provider.testFF'
|
|
11
|
+
testFF: 'confluence.fe.collab.provider.testFF',
|
|
12
|
+
socketMessageMetricsFF: 'confluence.fe.collab.provider.socketMessageMetricsFF'
|
|
11
13
|
}
|
|
12
14
|
};
|
|
13
15
|
const filterFeatureFlagNames = flags => {
|
|
@@ -13,8 +13,9 @@ export let EVENT_ACTION = /*#__PURE__*/function (EVENT_ACTION) {
|
|
|
13
13
|
EVENT_ACTION["SEND_STEPS_RETRY"] = "sendStepsRetry";
|
|
14
14
|
EVENT_ACTION["CATCHUP_AFTER_MAX_SEND_STEPS_RETRY"] = "catchupAfterMaxSendStepsRetry";
|
|
15
15
|
EVENT_ACTION["DROPPED_STEPS"] = "droppedStepInCatchup";
|
|
16
|
+
EVENT_ACTION["WEBSOCKET_MESSAGE_VOLUME_METRIC"] = "websocketMessageVolumeMetric";
|
|
16
17
|
return EVENT_ACTION;
|
|
17
|
-
}({}); // https://data-portal.internal.atlassian.com/analytics/registry/
|
|
18
|
+
}({}); // https://data-portal.internal.atlassian.com/analytics/registry/53596
|
|
18
19
|
export let EVENT_STATUS = /*#__PURE__*/function (EVENT_STATUS) {
|
|
19
20
|
EVENT_STATUS["SUCCESS"] = "SUCCESS";
|
|
20
21
|
EVENT_STATUS["FAILURE"] = "FAILURE";
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
2
|
+
import { EVENT_ACTION, EVENT_STATUS } from './const';
|
|
3
|
+
import { createLogger } from './utils';
|
|
4
|
+
const logger = createLogger('SocketMessageMetrics', 'green');
|
|
5
|
+
export const WEBSOCKET_MESSAGE_VOLUME_METRIC_SEND_INTERVAL_MS = 60000;
|
|
6
|
+
export class SocketMessageMetrics {
|
|
7
|
+
constructor(socket, analyticsHelper) {
|
|
8
|
+
_defineProperty(this, "messageCount", 0);
|
|
9
|
+
_defineProperty(this, "totalMessageSize", 0);
|
|
10
|
+
_defineProperty(this, "metricsIntervalID", undefined);
|
|
11
|
+
_defineProperty(this, "socketMessageMetricsListener", (event, ...args) => {
|
|
12
|
+
this.messageCount++;
|
|
13
|
+
this.totalMessageSize += Buffer.byteLength(JSON.stringify(args), 'utf8');
|
|
14
|
+
});
|
|
15
|
+
_defineProperty(this, "setupSocketMessageMetrics", () => {
|
|
16
|
+
if (this.metricsIntervalID !== undefined) {
|
|
17
|
+
logger('calling setupSocketMessageMetrics function with metricsIntervalID that is not undefined');
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
this.socket.onAnyOutgoing(this.socketMessageMetricsListener);
|
|
21
|
+
|
|
22
|
+
// send metrics every 60 seconds
|
|
23
|
+
this.metricsIntervalID = window.setInterval(() => {
|
|
24
|
+
this.analyticsHelper.sendActionEvent(EVENT_ACTION.WEBSOCKET_MESSAGE_VOLUME_METRIC, EVENT_STATUS.INFO, {
|
|
25
|
+
messageCount: this.messageCount,
|
|
26
|
+
totalMessageSize: this.totalMessageSize
|
|
27
|
+
});
|
|
28
|
+
this.messageCount = 0;
|
|
29
|
+
this.totalMessageSize = 0;
|
|
30
|
+
}, WEBSOCKET_MESSAGE_VOLUME_METRIC_SEND_INTERVAL_MS);
|
|
31
|
+
});
|
|
32
|
+
_defineProperty(this, "closeSocketMessageMetrics", () => {
|
|
33
|
+
clearInterval(this.metricsIntervalID);
|
|
34
|
+
this.metricsIntervalID = undefined;
|
|
35
|
+
this.socket.offAnyOutgoing(this.socketMessageMetricsListener);
|
|
36
|
+
});
|
|
37
|
+
this.socket = socket;
|
|
38
|
+
this.analyticsHelper = analyticsHelper;
|
|
39
|
+
}
|
|
40
|
+
}
|