@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/dist/es2019/version.json
CHANGED
package/dist/esm/channel.js
CHANGED
|
@@ -6,6 +6,9 @@ import _inherits from "@babel/runtime/helpers/inherits";
|
|
|
6
6
|
import _possibleConstructorReturn from "@babel/runtime/helpers/possibleConstructorReturn";
|
|
7
7
|
import _getPrototypeOf from "@babel/runtime/helpers/getPrototypeOf";
|
|
8
8
|
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
9
|
+
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; } } }; }
|
|
10
|
+
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); }
|
|
11
|
+
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; }
|
|
9
12
|
import _regeneratorRuntime from "@babel/runtime/regenerator";
|
|
10
13
|
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; }
|
|
11
14
|
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; }
|
|
@@ -20,7 +23,9 @@ import ReconnectHelper from './connectivity/reconnect-helper';
|
|
|
20
23
|
import { createDocInitExp } from './analytics/ufo';
|
|
21
24
|
import { socketIOReasons } from './disconnected-reason-mapper';
|
|
22
25
|
import Network from './connectivity/network';
|
|
23
|
-
import { NotConnectedError, NotInitializedError, INTERNAL_ERROR_CODE } from './errors/error-types';
|
|
26
|
+
import { NotConnectedError, NotInitializedError, INTERNAL_ERROR_CODE, NCS_ERROR_CODE } from './errors/error-types';
|
|
27
|
+
import { SocketMessageMetrics } from './helpers/socket-message-metrics';
|
|
28
|
+
import { getCollabProviderFeatureFlag } from './feature-flags';
|
|
24
29
|
var logger = createLogger('Channel', 'green');
|
|
25
30
|
export var Channel = /*#__PURE__*/function (_Emitter) {
|
|
26
31
|
_inherits(Channel, _Emitter);
|
|
@@ -29,11 +34,19 @@ export var Channel = /*#__PURE__*/function (_Emitter) {
|
|
|
29
34
|
var _this;
|
|
30
35
|
_classCallCheck(this, Channel);
|
|
31
36
|
_this = _super.call(this);
|
|
37
|
+
_defineProperty(_assertThisInitialized(_this), "RATE_LIMIT_TYPE_NONE", 0);
|
|
38
|
+
_defineProperty(_assertThisInitialized(_this), "RATE_LIMIT_TYPE_SOFT", 1);
|
|
39
|
+
_defineProperty(_assertThisInitialized(_this), "RATE_LIMIT_TYPE_HARD", 2);
|
|
32
40
|
_defineProperty(_assertThisInitialized(_this), "connected", false);
|
|
33
41
|
_defineProperty(_assertThisInitialized(_this), "socket", null);
|
|
34
42
|
_defineProperty(_assertThisInitialized(_this), "reconnectHelper", null);
|
|
35
43
|
_defineProperty(_assertThisInitialized(_this), "initialized", false);
|
|
36
44
|
_defineProperty(_assertThisInitialized(_this), "network", null);
|
|
45
|
+
_defineProperty(_assertThisInitialized(_this), "rateLimitWindowDurationMs", 60000);
|
|
46
|
+
_defineProperty(_assertThisInitialized(_this), "rateLimitWindowStartMs", 0);
|
|
47
|
+
_defineProperty(_assertThisInitialized(_this), "stepCounter", 0);
|
|
48
|
+
_defineProperty(_assertThisInitialized(_this), "stepSizeCounter", 0);
|
|
49
|
+
_defineProperty(_assertThisInitialized(_this), "maxStepSize", 0);
|
|
37
50
|
// read-only getters used for tests
|
|
38
51
|
_defineProperty(_assertThisInitialized(_this), "getInitialized", function () {
|
|
39
52
|
return _this.initialized;
|
|
@@ -63,7 +76,7 @@ export var Channel = /*#__PURE__*/function (_Emitter) {
|
|
|
63
76
|
reason: data.reason,
|
|
64
77
|
// Potentially incorrect when value of token changes between connecting and connect.
|
|
65
78
|
// See: https://bitbucket.org/atlassian/%7Bc8e2f021-38d2-46d0-9b7a-b3f7b428f724%7D/pull-requests/29905#comment-375308874
|
|
66
|
-
usedCachedToken: _this.token
|
|
79
|
+
usedCachedToken: !!_this.token
|
|
67
80
|
});
|
|
68
81
|
_this.unsetToken();
|
|
69
82
|
});
|
|
@@ -74,7 +87,7 @@ export var Channel = /*#__PURE__*/function (_Emitter) {
|
|
|
74
87
|
latency: measure === null || measure === void 0 ? void 0 : measure.duration,
|
|
75
88
|
// Potentially incorrect when value of token changes between connecting and connect.
|
|
76
89
|
// See: https://bitbucket.org/atlassian/%7Bc8e2f021-38d2-46d0-9b7a-b3f7b428f724%7D/pull-requests/29905#comment-375308874
|
|
77
|
-
usedCachedToken: _this.token
|
|
90
|
+
usedCachedToken: !!_this.token
|
|
78
91
|
});
|
|
79
92
|
(_this$analyticsHelper3 = _this.analyticsHelper) === null || _this$analyticsHelper3 === void 0 ? void 0 : _this$analyticsHelper3.sendErrorEvent(error, 'Error while establishing connection');
|
|
80
93
|
// If error received with `data`, it means the connection is rejected
|
|
@@ -118,6 +131,9 @@ export var Channel = /*#__PURE__*/function (_Emitter) {
|
|
|
118
131
|
});
|
|
119
132
|
_defineProperty(_assertThisInitialized(_this), "onConnect", function () {
|
|
120
133
|
var _this$analyticsHelper5;
|
|
134
|
+
if (getCollabProviderFeatureFlag('socketMessageMetricsFF', _this.config.featureFlags) && _this.socketMessageMetrics) {
|
|
135
|
+
_this.socketMessageMetrics.setupSocketMessageMetrics();
|
|
136
|
+
}
|
|
121
137
|
_this.connected = true;
|
|
122
138
|
logger('Connected.', _this.socket.id);
|
|
123
139
|
var measure = stopMeasure(MEASURE_NAME.SOCKET_CONNECT, _this.analyticsHelper);
|
|
@@ -125,7 +141,7 @@ export var Channel = /*#__PURE__*/function (_Emitter) {
|
|
|
125
141
|
latency: measure === null || measure === void 0 ? void 0 : measure.duration,
|
|
126
142
|
// Potentially incorrect when value of token changes between connecting and connect.
|
|
127
143
|
// See: https://bitbucket.org/atlassian/%7Bc8e2f021-38d2-46d0-9b7a-b3f7b428f724%7D/pull-requests/29905#comment-375308874
|
|
128
|
-
usedCachedToken: _this.token
|
|
144
|
+
usedCachedToken: !!_this.token
|
|
129
145
|
});
|
|
130
146
|
_this.emit('connected', {
|
|
131
147
|
sid: _this.socket.id,
|
|
@@ -467,6 +483,9 @@ export var Channel = /*#__PURE__*/function (_Emitter) {
|
|
|
467
483
|
};
|
|
468
484
|
}
|
|
469
485
|
this.socket = createSocket("".concat(url, "/session/").concat(documentAri), auth, this.config.productInfo);
|
|
486
|
+
if (this.socket && this.analyticsHelper) {
|
|
487
|
+
this.socketMessageMetrics = new SocketMessageMetrics(this.socket, this.analyticsHelper);
|
|
488
|
+
}
|
|
470
489
|
|
|
471
490
|
// Due to https://github.com/socketio/socket.io-client/issues/1473,
|
|
472
491
|
// reconnect no longer fired on the socket.
|
|
@@ -518,6 +537,9 @@ export var Channel = /*#__PURE__*/function (_Emitter) {
|
|
|
518
537
|
return _regeneratorRuntime.wrap(function _callee3$(_context3) {
|
|
519
538
|
while (1) switch (_context3.prev = _context3.next) {
|
|
520
539
|
case 0:
|
|
540
|
+
if (getCollabProviderFeatureFlag('socketMessageMetricsFF', _this2.config.featureFlags) && _this2.socketMessageMetrics) {
|
|
541
|
+
_this2.socketMessageMetrics.closeSocketMessageMetrics();
|
|
542
|
+
}
|
|
521
543
|
_this2.connected = false;
|
|
522
544
|
logger("disconnect reason: ".concat(reason));
|
|
523
545
|
_this2.emit('disconnect', {
|
|
@@ -539,7 +561,7 @@ export var Channel = /*#__PURE__*/function (_Emitter) {
|
|
|
539
561
|
_this2.emit('error', reconnectionError);
|
|
540
562
|
}
|
|
541
563
|
}
|
|
542
|
-
case
|
|
564
|
+
case 5:
|
|
543
565
|
case "end":
|
|
544
566
|
return _context3.stop();
|
|
545
567
|
}
|
|
@@ -559,6 +581,12 @@ export var Channel = /*#__PURE__*/function (_Emitter) {
|
|
|
559
581
|
// Ensure the error emit to the provider has the same structure, so we can handle them unified.
|
|
560
582
|
this.socket.on('connect_error', this.onConnectError);
|
|
561
583
|
this.socket.on('permission:invalidateToken', this.handlePermissionInvalidateToken);
|
|
584
|
+
this.socket.onAnyOutgoing(function (event) {
|
|
585
|
+
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
|
|
586
|
+
args[_key - 1] = arguments[_key];
|
|
587
|
+
}
|
|
588
|
+
return _this2.onAnyOutgoingHandler(Date.now(), args);
|
|
589
|
+
});
|
|
562
590
|
|
|
563
591
|
// To trigger reconnection when browser comes back online
|
|
564
592
|
if (!this.network) {
|
|
@@ -572,6 +600,81 @@ export var Channel = /*#__PURE__*/function (_Emitter) {
|
|
|
572
600
|
// Fired upon a reconnection attempt error (from Socket.IO Manager)
|
|
573
601
|
this.socket.io.on('reconnect_error', this.onReconnectError);
|
|
574
602
|
}
|
|
603
|
+
}, {
|
|
604
|
+
key: "onAnyOutgoingHandler",
|
|
605
|
+
value: function onAnyOutgoingHandler(currentTimeMs, args) {
|
|
606
|
+
var rateLimitType = this.config.rateLimitType || this.RATE_LIMIT_TYPE_NONE;
|
|
607
|
+
if (rateLimitType === this.RATE_LIMIT_TYPE_NONE) {
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
var stepLimit = this.config.rateLimitStepCount || 0;
|
|
611
|
+
var stepSizeLimit = this.config.rateLimitTotalStepSize || 0;
|
|
612
|
+
var maxStepSizeLimit = this.config.rateLimitMaxStepSize || 0;
|
|
613
|
+
var _iterator = _createForOfIteratorHelper(args),
|
|
614
|
+
_step;
|
|
615
|
+
try {
|
|
616
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
617
|
+
var arg = _step.value;
|
|
618
|
+
if (arg.type === 'steps:commit') {
|
|
619
|
+
if (currentTimeMs - this.rateLimitWindowStartMs > this.rateLimitWindowDurationMs) {
|
|
620
|
+
// Start a new window
|
|
621
|
+
this.rateLimitWindowStartMs = currentTimeMs;
|
|
622
|
+
this.stepCounter = 0;
|
|
623
|
+
this.stepSizeCounter = 0;
|
|
624
|
+
this.maxStepSize = 0;
|
|
625
|
+
}
|
|
626
|
+
this.stepCounter += arg.steps.length;
|
|
627
|
+
var _iterator2 = _createForOfIteratorHelper(arg.steps),
|
|
628
|
+
_step2;
|
|
629
|
+
try {
|
|
630
|
+
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
|
|
631
|
+
var step = _step2.value;
|
|
632
|
+
var stepSize = JSON.stringify(step).length;
|
|
633
|
+
this.stepSizeCounter += stepSize;
|
|
634
|
+
if (stepSize > this.maxStepSize) {
|
|
635
|
+
this.maxStepSize = stepSize;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
} catch (err) {
|
|
639
|
+
_iterator2.e(err);
|
|
640
|
+
} finally {
|
|
641
|
+
_iterator2.f();
|
|
642
|
+
}
|
|
643
|
+
if (this.isLimitExceeded(stepLimit, stepSizeLimit, maxStepSizeLimit)) {
|
|
644
|
+
var rateLimitError = {
|
|
645
|
+
message: 'Rate limited',
|
|
646
|
+
data: {
|
|
647
|
+
code: NCS_ERROR_CODE.RATE_LIMIT_ERROR,
|
|
648
|
+
status: 500,
|
|
649
|
+
meta: {
|
|
650
|
+
rateLimitType: rateLimitType,
|
|
651
|
+
maxStepSize: this.maxStepSize,
|
|
652
|
+
stepSizeCounter: this.stepSizeCounter,
|
|
653
|
+
stepCounter: this.stepCounter
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
};
|
|
657
|
+
if (rateLimitType === this.RATE_LIMIT_TYPE_HARD) {
|
|
658
|
+
this.emit('error', rateLimitError);
|
|
659
|
+
throw new Error();
|
|
660
|
+
} else if (rateLimitType === this.RATE_LIMIT_TYPE_SOFT) {
|
|
661
|
+
var _this$analyticsHelper7;
|
|
662
|
+
(_this$analyticsHelper7 = this.analyticsHelper) === null || _this$analyticsHelper7 === void 0 ? void 0 : _this$analyticsHelper7.sendErrorEvent(rateLimitError, 'Rate limited');
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
} catch (err) {
|
|
668
|
+
_iterator.e(err);
|
|
669
|
+
} finally {
|
|
670
|
+
_iterator.f();
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
}, {
|
|
674
|
+
key: "isLimitExceeded",
|
|
675
|
+
value: function isLimitExceeded(stepLimit, stepSizeLimit, maxStepSizeLimit) {
|
|
676
|
+
return stepLimit > 0 && this.stepCounter > stepLimit || stepSizeLimit > 0 && this.stepSizeCounter > stepSizeLimit || maxStepSizeLimit > 0 && this.maxStepSize > maxStepSizeLimit;
|
|
677
|
+
}
|
|
575
678
|
}, {
|
|
576
679
|
key: "disconnect",
|
|
577
680
|
value: function disconnect() {
|
|
@@ -99,7 +99,7 @@ export var errorCodeMapper = function 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 var errorCodeMapper = function 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
|
}
|
|
@@ -41,6 +41,7 @@ export var NCS_ERROR_CODE = /*#__PURE__*/function (NCS_ERROR_CODE) {
|
|
|
41
41
|
NCS_ERROR_CODE["INVALID_ACTIVATION_ID"] = "INVALID_ACTIVATION_ID";
|
|
42
42
|
NCS_ERROR_CODE["INVALID_DOCUMENT_ARI"] = "INVALID_DOCUMENT_ARI";
|
|
43
43
|
NCS_ERROR_CODE["INVALID_CLOUD_ID"] = "INVALID_CLOUD_ID";
|
|
44
|
+
NCS_ERROR_CODE["RATE_LIMIT_ERROR"] = "RATE_LIMIT_ERROR";
|
|
44
45
|
return NCS_ERROR_CODE;
|
|
45
46
|
}({});
|
|
46
47
|
|
|
@@ -58,6 +59,10 @@ export var NCS_ERROR_CODE = /*#__PURE__*/function (NCS_ERROR_CODE) {
|
|
|
58
59
|
* When we try to apply state updates to the editor, if that fails to apply the user can enter an invalid state where no
|
|
59
60
|
* changes can be saved to NCS.
|
|
60
61
|
*/
|
|
62
|
+
/**
|
|
63
|
+
* The client is trying to send too many messages or messages that are too large. This not expected to be a standard
|
|
64
|
+
* operating condition and should only ever indicate a frontend bug.
|
|
65
|
+
*/
|
|
61
66
|
/**
|
|
62
67
|
* A union of all possible internal errors, that are mapped to another error if being emitted to the editor.
|
|
63
68
|
*/
|
|
@@ -2,9 +2,10 @@ import { getProductSpecificFeatureFlags, getCollabProviderFeatureFlag } from '..
|
|
|
2
2
|
describe('Feature flags', function () {
|
|
3
3
|
it('getProductSpecificFeatureFlags', function () {
|
|
4
4
|
var 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', function () {
|
|
10
11
|
var result = getCollabProviderFeatureFlag('testFF', {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
|
|
2
2
|
var defaultNCSFeatureFlags = {
|
|
3
|
-
testFF: false
|
|
3
|
+
testFF: false,
|
|
4
|
+
socketMessageMetricsFF: false
|
|
4
5
|
};
|
|
5
6
|
|
|
6
7
|
/**
|
|
@@ -8,7 +9,8 @@ var defaultNCSFeatureFlags = {
|
|
|
8
9
|
*/
|
|
9
10
|
var productKeys = {
|
|
10
11
|
confluence: {
|
|
11
|
-
testFF: 'confluence.fe.collab.provider.testFF'
|
|
12
|
+
testFF: 'confluence.fe.collab.provider.testFF',
|
|
13
|
+
socketMessageMetricsFF: 'confluence.fe.collab.provider.socketMessageMetricsFF'
|
|
12
14
|
}
|
|
13
15
|
};
|
|
14
16
|
var filterFeatureFlagNames = function filterFeatureFlagNames(flags) {
|
|
@@ -13,8 +13,9 @@ export var 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 var EVENT_STATUS = /*#__PURE__*/function (EVENT_STATUS) {
|
|
19
20
|
EVENT_STATUS["SUCCESS"] = "SUCCESS";
|
|
20
21
|
EVENT_STATUS["FAILURE"] = "FAILURE";
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import _createClass from "@babel/runtime/helpers/createClass";
|
|
2
|
+
import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
|
|
3
|
+
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
4
|
+
import { EVENT_ACTION, EVENT_STATUS } from './const';
|
|
5
|
+
import { createLogger } from './utils';
|
|
6
|
+
var logger = createLogger('SocketMessageMetrics', 'green');
|
|
7
|
+
export var WEBSOCKET_MESSAGE_VOLUME_METRIC_SEND_INTERVAL_MS = 60000;
|
|
8
|
+
export var SocketMessageMetrics = /*#__PURE__*/_createClass(function SocketMessageMetrics(socket, analyticsHelper) {
|
|
9
|
+
var _this = this;
|
|
10
|
+
_classCallCheck(this, SocketMessageMetrics);
|
|
11
|
+
_defineProperty(this, "messageCount", 0);
|
|
12
|
+
_defineProperty(this, "totalMessageSize", 0);
|
|
13
|
+
_defineProperty(this, "metricsIntervalID", undefined);
|
|
14
|
+
_defineProperty(this, "socketMessageMetricsListener", function (event) {
|
|
15
|
+
_this.messageCount++;
|
|
16
|
+
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
|
|
17
|
+
args[_key - 1] = arguments[_key];
|
|
18
|
+
}
|
|
19
|
+
_this.totalMessageSize += Buffer.byteLength(JSON.stringify(args), 'utf8');
|
|
20
|
+
});
|
|
21
|
+
_defineProperty(this, "setupSocketMessageMetrics", function () {
|
|
22
|
+
if (_this.metricsIntervalID !== undefined) {
|
|
23
|
+
logger('calling setupSocketMessageMetrics function with metricsIntervalID that is not undefined');
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
_this.socket.onAnyOutgoing(_this.socketMessageMetricsListener);
|
|
27
|
+
|
|
28
|
+
// send metrics every 60 seconds
|
|
29
|
+
_this.metricsIntervalID = window.setInterval(function () {
|
|
30
|
+
_this.analyticsHelper.sendActionEvent(EVENT_ACTION.WEBSOCKET_MESSAGE_VOLUME_METRIC, EVENT_STATUS.INFO, {
|
|
31
|
+
messageCount: _this.messageCount,
|
|
32
|
+
totalMessageSize: _this.totalMessageSize
|
|
33
|
+
});
|
|
34
|
+
_this.messageCount = 0;
|
|
35
|
+
_this.totalMessageSize = 0;
|
|
36
|
+
}, WEBSOCKET_MESSAGE_VOLUME_METRIC_SEND_INTERVAL_MS);
|
|
37
|
+
});
|
|
38
|
+
_defineProperty(this, "closeSocketMessageMetrics", function () {
|
|
39
|
+
clearInterval(_this.metricsIntervalID);
|
|
40
|
+
_this.metricsIntervalID = undefined;
|
|
41
|
+
_this.socket.offAnyOutgoing(_this.socketMessageMetricsListener);
|
|
42
|
+
});
|
|
43
|
+
this.socket = socket;
|
|
44
|
+
this.analyticsHelper = analyticsHelper;
|
|
45
|
+
});
|
package/dist/esm/version.json
CHANGED
package/dist/types/channel.d.ts
CHANGED
|
@@ -4,15 +4,24 @@ import type { Socket } from 'socket.io-client';
|
|
|
4
4
|
import AnalyticsHelper from './analytics/analytics-helper';
|
|
5
5
|
import type { Metadata } from '@atlaskit/editor-common/collab';
|
|
6
6
|
export declare class Channel extends Emitter<ChannelEvent> {
|
|
7
|
+
private readonly RATE_LIMIT_TYPE_NONE;
|
|
8
|
+
private readonly RATE_LIMIT_TYPE_SOFT;
|
|
9
|
+
private readonly RATE_LIMIT_TYPE_HARD;
|
|
7
10
|
private connected;
|
|
8
|
-
private config;
|
|
11
|
+
private readonly config;
|
|
9
12
|
private socket;
|
|
10
13
|
private reconnectHelper?;
|
|
11
14
|
private initialized;
|
|
12
|
-
private analyticsHelper?;
|
|
15
|
+
private readonly analyticsHelper?;
|
|
13
16
|
private initExperience?;
|
|
14
17
|
private token?;
|
|
15
18
|
private network;
|
|
19
|
+
private socketMessageMetrics?;
|
|
20
|
+
private readonly rateLimitWindowDurationMs;
|
|
21
|
+
private rateLimitWindowStartMs;
|
|
22
|
+
private stepCounter;
|
|
23
|
+
private stepSizeCounter;
|
|
24
|
+
private maxStepSize;
|
|
16
25
|
constructor(config: Config, analyticsHelper: AnalyticsHelper);
|
|
17
26
|
getInitialized: () => boolean;
|
|
18
27
|
getConnected: () => boolean;
|
|
@@ -24,6 +33,8 @@ export declare class Channel extends Emitter<ChannelEvent> {
|
|
|
24
33
|
* Connect to collab service using websockets
|
|
25
34
|
*/
|
|
26
35
|
connect(shouldInitialize?: boolean): void;
|
|
36
|
+
onAnyOutgoingHandler(currentTimeMs: number, args: any[]): void;
|
|
37
|
+
private isLimitExceeded;
|
|
27
38
|
private handlePermissionInvalidateToken;
|
|
28
39
|
private onConnectError;
|
|
29
40
|
private onReconnectError;
|
|
@@ -26,7 +26,8 @@ export declare enum NCS_ERROR_CODE {
|
|
|
26
26
|
DYNAMO_ERROR = "DYNAMO_ERROR",
|
|
27
27
|
INVALID_ACTIVATION_ID = "INVALID_ACTIVATION_ID",
|
|
28
28
|
INVALID_DOCUMENT_ARI = "INVALID_DOCUMENT_ARI",
|
|
29
|
-
INVALID_CLOUD_ID = "INVALID_CLOUD_ID"
|
|
29
|
+
INVALID_CLOUD_ID = "INVALID_CLOUD_ID",
|
|
30
|
+
RATE_LIMIT_ERROR = "RATE_LIMIT_ERROR"
|
|
30
31
|
}
|
|
31
32
|
type HeadVersionUpdateFailedError = {
|
|
32
33
|
message: string;
|
|
@@ -236,10 +237,27 @@ export type InternalDocumentUpdateFailure = {
|
|
|
236
237
|
status: 500;
|
|
237
238
|
};
|
|
238
239
|
};
|
|
240
|
+
/**
|
|
241
|
+
* The client is trying to send too many messages or messages that are too large. This not expected to be a standard
|
|
242
|
+
* operating condition and should only ever indicate a frontend bug.
|
|
243
|
+
*/
|
|
244
|
+
export type RateLimitError = {
|
|
245
|
+
message: string;
|
|
246
|
+
data: {
|
|
247
|
+
code: NCS_ERROR_CODE.RATE_LIMIT_ERROR;
|
|
248
|
+
meta: {
|
|
249
|
+
rateLimitType: number;
|
|
250
|
+
maxStepSize: number;
|
|
251
|
+
stepSizeCounter: number;
|
|
252
|
+
stepCounter: number;
|
|
253
|
+
};
|
|
254
|
+
status: 500;
|
|
255
|
+
};
|
|
256
|
+
};
|
|
239
257
|
/**
|
|
240
258
|
* A union of all possible internal errors, that are mapped to another error if being emitted to the editor.
|
|
241
259
|
*/
|
|
242
|
-
export type InternalError = NCSErrors | DocumentRecoveryError | AddStepsError | CatchUpFailedError | TokenPermissionError | ReconnectionError | ConnectionError | ReconnectionNetworkError | DocumentNotFoundError | InternalDocumentUpdateFailure;
|
|
260
|
+
export type InternalError = NCSErrors | DocumentRecoveryError | AddStepsError | CatchUpFailedError | TokenPermissionError | ReconnectionError | ConnectionError | ReconnectionNetworkError | DocumentNotFoundError | InternalDocumentUpdateFailure | RateLimitError;
|
|
243
261
|
type ValidEventAttributeType = boolean | string | number;
|
|
244
262
|
export declare class CustomError extends Error {
|
|
245
263
|
extraEventAttributes?: {
|
|
@@ -13,7 +13,8 @@ export declare enum EVENT_ACTION {
|
|
|
13
13
|
INVALIDATE_TOKEN = "invalidateToken",
|
|
14
14
|
SEND_STEPS_RETRY = "sendStepsRetry",
|
|
15
15
|
CATCHUP_AFTER_MAX_SEND_STEPS_RETRY = "catchupAfterMaxSendStepsRetry",
|
|
16
|
-
DROPPED_STEPS = "droppedStepInCatchup"
|
|
16
|
+
DROPPED_STEPS = "droppedStepInCatchup",
|
|
17
|
+
WEBSOCKET_MESSAGE_VOLUME_METRIC = "websocketMessageVolumeMetric"
|
|
17
18
|
}
|
|
18
19
|
export declare enum EVENT_STATUS {
|
|
19
20
|
SUCCESS = "SUCCESS",
|
|
@@ -215,7 +216,16 @@ type CatchupAfterMaxSendStepsRetryAnalyticsEvent = {
|
|
|
215
216
|
eventStatus: EVENT_STATUS.INFO;
|
|
216
217
|
};
|
|
217
218
|
};
|
|
218
|
-
|
|
219
|
+
type WebsocketMessageVolumeMetricEvent = {
|
|
220
|
+
eventAction: EVENT_ACTION.WEBSOCKET_MESSAGE_VOLUME_METRIC;
|
|
221
|
+
attributes: {
|
|
222
|
+
documentAri?: string;
|
|
223
|
+
eventStatus: EVENT_STATUS.INFO;
|
|
224
|
+
messageCount: number;
|
|
225
|
+
messageSize: number;
|
|
226
|
+
};
|
|
227
|
+
};
|
|
228
|
+
export type ActionAnalyticsEvent = AddStepsSuccessAnalyticsEvent | AddStepsFailureAnalyticsEvent | ReInitDocFailAnalyticsEvent | ReInitDocSuccessAnalyticsEvent | ConnectionSuccessAnalyticsEvent | ConnectionFailureAnalyticsEvent | CatchUpSuccessAnalyticsEvent | CatchUpFailureAnalyticsEvent | DocumentInitSuccessAnalyticsEvent | UpdateParticipantsSuccessAnalyticsEvent | CommitUnconfirmedStepsSuccessAnalyticsEvent | CommitUnconfirmedStepsFailureAnalyticsEvent | PublishPageSuccessAnalyticsEvent | PublishPageFailureAnalyticsEvent | GetCurrentStateSuccessAnalyticsEvent | GetCurrentStateFailureAnalyticsEvent | InvalidateTokenAnalyticsEvent | SendStepsRetryAnalyticsEvent | CatchupAfterMaxSendStepsRetryAnalyticsEvent | CatchUpDroppedStepsEvent | WebsocketMessageVolumeMetricEvent;
|
|
219
229
|
export declare const ACK_MAX_TRY = 60;
|
|
220
230
|
export declare const CONFLUENCE = "confluence";
|
|
221
231
|
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import AnalyticsHelper from '../analytics/analytics-helper';
|
|
2
|
+
import type { Socket } from 'socket.io-client';
|
|
3
|
+
export declare const WEBSOCKET_MESSAGE_VOLUME_METRIC_SEND_INTERVAL_MS = 60000;
|
|
4
|
+
export declare class SocketMessageMetrics {
|
|
5
|
+
private messageCount;
|
|
6
|
+
private totalMessageSize;
|
|
7
|
+
private metricsIntervalID;
|
|
8
|
+
private socket;
|
|
9
|
+
private analyticsHelper;
|
|
10
|
+
constructor(socket: Socket, analyticsHelper: AnalyticsHelper);
|
|
11
|
+
socketMessageMetricsListener: (event: any, ...args: any[]) => void;
|
|
12
|
+
setupSocketMessageMetrics: () => void;
|
|
13
|
+
closeSocketMessageMetrics: () => void;
|
|
14
|
+
}
|
package/dist/types/types.d.ts
CHANGED
|
@@ -63,6 +63,15 @@ export interface Config {
|
|
|
63
63
|
* throwing a non-recoverable error if it's detected.
|
|
64
64
|
*/
|
|
65
65
|
enableErrorOnFailedDocumentApply?: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Configure the client side circuit breaker in the event that abnormal behaviour causes the client to flood
|
|
68
|
+
* NCS with too many steps or too large a volume of data. This can result in either a soft fail or a hard (fatal) fail
|
|
69
|
+
* depending on the configured rate limit type.
|
|
70
|
+
*/
|
|
71
|
+
rateLimitMaxStepSize?: number;
|
|
72
|
+
rateLimitStepCount?: number;
|
|
73
|
+
rateLimitTotalStepSize?: number;
|
|
74
|
+
rateLimitType?: number;
|
|
66
75
|
}
|
|
67
76
|
export interface InitAndAuthData {
|
|
68
77
|
initialized: boolean;
|
|
@@ -4,15 +4,24 @@ import type { Socket } from 'socket.io-client';
|
|
|
4
4
|
import AnalyticsHelper from './analytics/analytics-helper';
|
|
5
5
|
import type { Metadata } from '@atlaskit/editor-common/collab';
|
|
6
6
|
export declare class Channel extends Emitter<ChannelEvent> {
|
|
7
|
+
private readonly RATE_LIMIT_TYPE_NONE;
|
|
8
|
+
private readonly RATE_LIMIT_TYPE_SOFT;
|
|
9
|
+
private readonly RATE_LIMIT_TYPE_HARD;
|
|
7
10
|
private connected;
|
|
8
|
-
private config;
|
|
11
|
+
private readonly config;
|
|
9
12
|
private socket;
|
|
10
13
|
private reconnectHelper?;
|
|
11
14
|
private initialized;
|
|
12
|
-
private analyticsHelper?;
|
|
15
|
+
private readonly analyticsHelper?;
|
|
13
16
|
private initExperience?;
|
|
14
17
|
private token?;
|
|
15
18
|
private network;
|
|
19
|
+
private socketMessageMetrics?;
|
|
20
|
+
private readonly rateLimitWindowDurationMs;
|
|
21
|
+
private rateLimitWindowStartMs;
|
|
22
|
+
private stepCounter;
|
|
23
|
+
private stepSizeCounter;
|
|
24
|
+
private maxStepSize;
|
|
16
25
|
constructor(config: Config, analyticsHelper: AnalyticsHelper);
|
|
17
26
|
getInitialized: () => boolean;
|
|
18
27
|
getConnected: () => boolean;
|
|
@@ -24,6 +33,8 @@ export declare class Channel extends Emitter<ChannelEvent> {
|
|
|
24
33
|
* Connect to collab service using websockets
|
|
25
34
|
*/
|
|
26
35
|
connect(shouldInitialize?: boolean): void;
|
|
36
|
+
onAnyOutgoingHandler(currentTimeMs: number, args: any[]): void;
|
|
37
|
+
private isLimitExceeded;
|
|
27
38
|
private handlePermissionInvalidateToken;
|
|
28
39
|
private onConnectError;
|
|
29
40
|
private onReconnectError;
|
|
@@ -26,7 +26,8 @@ export declare enum NCS_ERROR_CODE {
|
|
|
26
26
|
DYNAMO_ERROR = "DYNAMO_ERROR",
|
|
27
27
|
INVALID_ACTIVATION_ID = "INVALID_ACTIVATION_ID",
|
|
28
28
|
INVALID_DOCUMENT_ARI = "INVALID_DOCUMENT_ARI",
|
|
29
|
-
INVALID_CLOUD_ID = "INVALID_CLOUD_ID"
|
|
29
|
+
INVALID_CLOUD_ID = "INVALID_CLOUD_ID",
|
|
30
|
+
RATE_LIMIT_ERROR = "RATE_LIMIT_ERROR"
|
|
30
31
|
}
|
|
31
32
|
type HeadVersionUpdateFailedError = {
|
|
32
33
|
message: string;
|
|
@@ -236,10 +237,27 @@ export type InternalDocumentUpdateFailure = {
|
|
|
236
237
|
status: 500;
|
|
237
238
|
};
|
|
238
239
|
};
|
|
240
|
+
/**
|
|
241
|
+
* The client is trying to send too many messages or messages that are too large. This not expected to be a standard
|
|
242
|
+
* operating condition and should only ever indicate a frontend bug.
|
|
243
|
+
*/
|
|
244
|
+
export type RateLimitError = {
|
|
245
|
+
message: string;
|
|
246
|
+
data: {
|
|
247
|
+
code: NCS_ERROR_CODE.RATE_LIMIT_ERROR;
|
|
248
|
+
meta: {
|
|
249
|
+
rateLimitType: number;
|
|
250
|
+
maxStepSize: number;
|
|
251
|
+
stepSizeCounter: number;
|
|
252
|
+
stepCounter: number;
|
|
253
|
+
};
|
|
254
|
+
status: 500;
|
|
255
|
+
};
|
|
256
|
+
};
|
|
239
257
|
/**
|
|
240
258
|
* A union of all possible internal errors, that are mapped to another error if being emitted to the editor.
|
|
241
259
|
*/
|
|
242
|
-
export type InternalError = NCSErrors | DocumentRecoveryError | AddStepsError | CatchUpFailedError | TokenPermissionError | ReconnectionError | ConnectionError | ReconnectionNetworkError | DocumentNotFoundError | InternalDocumentUpdateFailure;
|
|
260
|
+
export type InternalError = NCSErrors | DocumentRecoveryError | AddStepsError | CatchUpFailedError | TokenPermissionError | ReconnectionError | ConnectionError | ReconnectionNetworkError | DocumentNotFoundError | InternalDocumentUpdateFailure | RateLimitError;
|
|
243
261
|
type ValidEventAttributeType = boolean | string | number;
|
|
244
262
|
export declare class CustomError extends Error {
|
|
245
263
|
extraEventAttributes?: {
|
|
@@ -13,7 +13,8 @@ export declare enum EVENT_ACTION {
|
|
|
13
13
|
INVALIDATE_TOKEN = "invalidateToken",
|
|
14
14
|
SEND_STEPS_RETRY = "sendStepsRetry",
|
|
15
15
|
CATCHUP_AFTER_MAX_SEND_STEPS_RETRY = "catchupAfterMaxSendStepsRetry",
|
|
16
|
-
DROPPED_STEPS = "droppedStepInCatchup"
|
|
16
|
+
DROPPED_STEPS = "droppedStepInCatchup",
|
|
17
|
+
WEBSOCKET_MESSAGE_VOLUME_METRIC = "websocketMessageVolumeMetric"
|
|
17
18
|
}
|
|
18
19
|
export declare enum EVENT_STATUS {
|
|
19
20
|
SUCCESS = "SUCCESS",
|
|
@@ -215,7 +216,16 @@ type CatchupAfterMaxSendStepsRetryAnalyticsEvent = {
|
|
|
215
216
|
eventStatus: EVENT_STATUS.INFO;
|
|
216
217
|
};
|
|
217
218
|
};
|
|
218
|
-
|
|
219
|
+
type WebsocketMessageVolumeMetricEvent = {
|
|
220
|
+
eventAction: EVENT_ACTION.WEBSOCKET_MESSAGE_VOLUME_METRIC;
|
|
221
|
+
attributes: {
|
|
222
|
+
documentAri?: string;
|
|
223
|
+
eventStatus: EVENT_STATUS.INFO;
|
|
224
|
+
messageCount: number;
|
|
225
|
+
messageSize: number;
|
|
226
|
+
};
|
|
227
|
+
};
|
|
228
|
+
export type ActionAnalyticsEvent = AddStepsSuccessAnalyticsEvent | AddStepsFailureAnalyticsEvent | ReInitDocFailAnalyticsEvent | ReInitDocSuccessAnalyticsEvent | ConnectionSuccessAnalyticsEvent | ConnectionFailureAnalyticsEvent | CatchUpSuccessAnalyticsEvent | CatchUpFailureAnalyticsEvent | DocumentInitSuccessAnalyticsEvent | UpdateParticipantsSuccessAnalyticsEvent | CommitUnconfirmedStepsSuccessAnalyticsEvent | CommitUnconfirmedStepsFailureAnalyticsEvent | PublishPageSuccessAnalyticsEvent | PublishPageFailureAnalyticsEvent | GetCurrentStateSuccessAnalyticsEvent | GetCurrentStateFailureAnalyticsEvent | InvalidateTokenAnalyticsEvent | SendStepsRetryAnalyticsEvent | CatchupAfterMaxSendStepsRetryAnalyticsEvent | CatchUpDroppedStepsEvent | WebsocketMessageVolumeMetricEvent;
|
|
219
229
|
export declare const ACK_MAX_TRY = 60;
|
|
220
230
|
export declare const CONFLUENCE = "confluence";
|
|
221
231
|
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import AnalyticsHelper from '../analytics/analytics-helper';
|
|
2
|
+
import type { Socket } from 'socket.io-client';
|
|
3
|
+
export declare const WEBSOCKET_MESSAGE_VOLUME_METRIC_SEND_INTERVAL_MS = 60000;
|
|
4
|
+
export declare class SocketMessageMetrics {
|
|
5
|
+
private messageCount;
|
|
6
|
+
private totalMessageSize;
|
|
7
|
+
private metricsIntervalID;
|
|
8
|
+
private socket;
|
|
9
|
+
private analyticsHelper;
|
|
10
|
+
constructor(socket: Socket, analyticsHelper: AnalyticsHelper);
|
|
11
|
+
socketMessageMetricsListener: (event: any, ...args: any[]) => void;
|
|
12
|
+
setupSocketMessageMetrics: () => void;
|
|
13
|
+
closeSocketMessageMetrics: () => void;
|
|
14
|
+
}
|