@atlaskit/collab-provider 10.20.2 → 10.20.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 +9 -0
- package/dist/cjs/document/document-service.js +8 -8
- package/dist/cjs/participants/participants-service.js +4 -2
- package/dist/cjs/provider/commit-step.js +215 -180
- package/dist/cjs/version-wrapper.js +1 -1
- package/dist/es2019/document/document-service.js +9 -9
- package/dist/es2019/participants/participants-service.js +3 -1
- package/dist/es2019/provider/commit-step.js +203 -183
- package/dist/es2019/version-wrapper.js +1 -1
- package/dist/esm/document/document-service.js +9 -9
- package/dist/esm/participants/participants-service.js +3 -1
- package/dist/esm/provider/commit-step.js +214 -179
- package/dist/esm/version-wrapper.js +1 -1
- package/dist/types/document/document-service.d.ts +2 -1
- package/dist/types/participants/participants-service.d.ts +2 -0
- package/dist/types/provider/commit-step.d.ts +33 -20
- package/dist/types-ts4.5/document/document-service.d.ts +2 -1
- package/dist/types-ts4.5/participants/participants-service.d.ts +2 -0
- package/dist/types-ts4.5/provider/commit-step.d.ts +33 -20
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# @atlaskit/collab-provider
|
|
2
2
|
|
|
3
|
+
## 10.20.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#173357](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/pull-requests/173357)
|
|
8
|
+
[`f17a667b25b42`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/f17a667b25b42) -
|
|
9
|
+
enable merging of prosemirror steps for single player sessions
|
|
10
|
+
- Updated dependencies
|
|
11
|
+
|
|
3
12
|
## 10.20.2
|
|
4
13
|
|
|
5
14
|
### Patch Changes
|
|
@@ -16,10 +16,12 @@ var _prosemirrorCollab = require("@atlaskit/prosemirror-collab");
|
|
|
16
16
|
var _state = require("@atlaskit/editor-prosemirror/state");
|
|
17
17
|
var _editorJsonTransformer = require("@atlaskit/editor-json-transformer");
|
|
18
18
|
var _experiments = require("@atlaskit/tmp-editor-statsig/experiments");
|
|
19
|
+
var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
|
|
19
20
|
var _const = require("../helpers/const");
|
|
20
21
|
var _performance = require("../analytics/performance");
|
|
21
22
|
var _internalErrors = require("../errors/internal-errors");
|
|
22
23
|
var _utils = require("../helpers/utils");
|
|
24
|
+
var _participantsService = require("../participants/participants-service");
|
|
23
25
|
var _provider = require("../provider");
|
|
24
26
|
var _commitStep = require("../provider/commit-step");
|
|
25
27
|
var _customErrors = require("../errors/custom-errors");
|
|
@@ -807,6 +809,7 @@ var DocumentService = exports.DocumentService = /*#__PURE__*/function () {
|
|
|
807
809
|
this.getConnected = getConnected;
|
|
808
810
|
this.stepQueue = new _stepQueueState.StepQueueState();
|
|
809
811
|
this.onErrorHandled = onErrorHandled;
|
|
812
|
+
this.commitStepService = new _commitStep.CommitStepService(this.broadcast, this.analyticsHelper, this.providerEmitCallback, this.onErrorHandled);
|
|
810
813
|
}
|
|
811
814
|
return (0, _createClass2.default)(DocumentService, [{
|
|
812
815
|
key: "getVersionFromCollabState",
|
|
@@ -1000,8 +1003,9 @@ var DocumentService = exports.DocumentService = /*#__PURE__*/function () {
|
|
|
1000
1003
|
var version = this.getVersionFromCollabState(newState, 'collab-provider: send');
|
|
1001
1004
|
|
|
1002
1005
|
// Don't send any steps before we're ready.
|
|
1003
|
-
if ((0, _experiments.editorExperiment)('platform_editor_offline_editing_web', true)) {
|
|
1004
|
-
|
|
1006
|
+
if ((0, _experiments.editorExperiment)('platform_editor_offline_editing_web', true) || (0, _expValEquals.expValEquals)('platform_editor_enable_single_player_step_merging', 'isEnabled', true)) {
|
|
1007
|
+
var enableStepsMergingForSinglePlayer = (0, _expValEquals.expValEquals)('platform_editor_enable_single_player_step_merging', 'isEnabled', true) && !this.commitStepService.getReadyToCommitStatus() && this.participantsService.getCollabMode() === _participantsService.SINGLE_COLLAB_MODE;
|
|
1008
|
+
if (!unconfirmedStepsData || !this.getConnected() || enableStepsMergingForSinglePlayer) {
|
|
1005
1009
|
return;
|
|
1006
1010
|
}
|
|
1007
1011
|
} else {
|
|
@@ -1043,7 +1047,7 @@ var DocumentService = exports.DocumentService = /*#__PURE__*/function () {
|
|
|
1043
1047
|
// If we are going to commit unconfirmed steps
|
|
1044
1048
|
// we need to lock them to ensure they don't get
|
|
1045
1049
|
// mutated in: `packages/editor/editor-plugin-collab-edit/src/pm-plugins/mergeUnconfirmed.ts`
|
|
1046
|
-
if ((0, _experiments.editorExperiment)('platform_editor_offline_editing_web', true)) {
|
|
1050
|
+
if ((0, _experiments.editorExperiment)('platform_editor_offline_editing_web', true) || (0, _expValEquals.expValEquals)('platform_editor_enable_single_player_step_merging', 'isEnabled', true)) {
|
|
1047
1051
|
unconfirmedStepsData.origins.forEach(function (origin) {
|
|
1048
1052
|
if (origin instanceof _state.Transaction) {
|
|
1049
1053
|
return origin.setMeta('mergeIsLocked', true);
|
|
@@ -1061,8 +1065,7 @@ var DocumentService = exports.DocumentService = /*#__PURE__*/function () {
|
|
|
1061
1065
|
// Avoid reference issues using a
|
|
1062
1066
|
// method outside of the provider
|
|
1063
1067
|
// scope
|
|
1064
|
-
|
|
1065
|
-
broadcast: this.broadcast,
|
|
1068
|
+
this.commitStepService.commitStepQueue({
|
|
1066
1069
|
// Ignored via go/ees005
|
|
1067
1070
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
1068
1071
|
userId: this.getUserId(),
|
|
@@ -1072,9 +1075,6 @@ var DocumentService = exports.DocumentService = /*#__PURE__*/function () {
|
|
|
1072
1075
|
steps: unconfirmedSteps,
|
|
1073
1076
|
version: version,
|
|
1074
1077
|
onStepsAdded: this.onStepsAdded,
|
|
1075
|
-
onErrorHandled: this.onErrorHandled,
|
|
1076
|
-
analyticsHelper: this.analyticsHelper,
|
|
1077
|
-
emit: this.providerEmitCallback,
|
|
1078
1078
|
__livePage: this.options.__livePage,
|
|
1079
1079
|
hasRecovered: this.hasRecovered,
|
|
1080
1080
|
collabMode: this.participantsService.getCollabMode(),
|
|
@@ -4,7 +4,7 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
|
|
|
4
4
|
Object.defineProperty(exports, "__esModule", {
|
|
5
5
|
value: true
|
|
6
6
|
});
|
|
7
|
-
exports.ParticipantsService = void 0;
|
|
7
|
+
exports.SINGLE_COLLAB_MODE = exports.ParticipantsService = exports.MULTI_COLLAB_MODE = void 0;
|
|
8
8
|
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
|
|
9
9
|
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
|
|
10
10
|
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
|
|
@@ -22,6 +22,8 @@ var logger = (0, _utils.createLogger)('PresenceService', 'pink');
|
|
|
22
22
|
var SEND_PRESENCE_INTERVAL = 150 * 1000; // 150 seconds
|
|
23
23
|
var DEFAULT_FETCH_USERS_INTERVAL = 500; // 0.5 second
|
|
24
24
|
var UNIDENTIFIED = 'unidentified';
|
|
25
|
+
var SINGLE_COLLAB_MODE = exports.SINGLE_COLLAB_MODE = 'single';
|
|
26
|
+
var MULTI_COLLAB_MODE = exports.MULTI_COLLAB_MODE = 'collab';
|
|
25
27
|
|
|
26
28
|
/**
|
|
27
29
|
* This service is responsible for handling presence and participant events, as well as sending them on to the editor or NCS.
|
|
@@ -660,7 +662,7 @@ var ParticipantsService = exports.ParticipantsService = /*#__PURE__*/function ()
|
|
|
660
662
|
return _this.participantsState.getAIProviderParticipants();
|
|
661
663
|
});
|
|
662
664
|
(0, _defineProperty2.default)(this, "getCollabMode", function () {
|
|
663
|
-
return _this.participantsState.size() > 1 ?
|
|
665
|
+
return _this.participantsState.size() > 1 ? MULTI_COLLAB_MODE : SINGLE_COLLAB_MODE;
|
|
664
666
|
});
|
|
665
667
|
this.analyticsHelper = analyticsHelper;
|
|
666
668
|
this.participantsState = participantsState;
|
|
@@ -4,8 +4,10 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
|
|
|
4
4
|
Object.defineProperty(exports, "__esModule", {
|
|
5
5
|
value: true
|
|
6
6
|
});
|
|
7
|
-
exports.
|
|
7
|
+
exports.RESET_READYTOCOMMIT_INTERVAL_MS = exports.CommitStepService = void 0;
|
|
8
8
|
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
9
|
+
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
|
|
10
|
+
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
|
|
9
11
|
var _countBy = _interopRequireDefault(require("lodash/countBy"));
|
|
10
12
|
var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
|
|
11
13
|
var _featureGateJsClient = _interopRequireDefault(require("@atlaskit/feature-gate-js-client"));
|
|
@@ -16,200 +18,233 @@ var _utils = require("../helpers/utils");
|
|
|
16
18
|
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
17
19
|
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
18
20
|
var logger = (0, _utils.createLogger)('commit-step', 'black');
|
|
19
|
-
var readyToCommit = exports.readyToCommit = true;
|
|
20
|
-
var lastBroadcastRequestAcked = exports.lastBroadcastRequestAcked = true;
|
|
21
21
|
var RESET_READYTOCOMMIT_INTERVAL_MS = exports.RESET_READYTOCOMMIT_INTERVAL_MS = 5000;
|
|
22
|
-
var
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
// if publishing and not waiting for an ACK, then clear the commit timer and proceed, skipping the timer
|
|
41
|
-
if (reason === 'publish' && lastBroadcastRequestAcked) {
|
|
42
|
-
if ((0, _platformFeatureFlags.fg)('skip_collab_provider_delay_on_publish')) {
|
|
43
|
-
clearTimeout(commitWaitTimer);
|
|
44
|
-
exports.readyToCommit = readyToCommit = true;
|
|
45
|
-
} // no-op if fg is turned off
|
|
46
|
-
}
|
|
47
|
-
if (!readyToCommit) {
|
|
48
|
-
logger('Not ready to commit, skip');
|
|
49
|
-
return;
|
|
22
|
+
var CommitStepService = exports.CommitStepService = /*#__PURE__*/function () {
|
|
23
|
+
/**
|
|
24
|
+
* @param broadcast - Callback for broadcasting events to other clients
|
|
25
|
+
* @param analyticsHelper - Helper for analytics events
|
|
26
|
+
* @param emit - Callback for emitting events to listeners on the provider
|
|
27
|
+
* @param onErrorHandled - Callback to handle
|
|
28
|
+
*/
|
|
29
|
+
function CommitStepService(broadcast, analyticsHelper,
|
|
30
|
+
// Ignored via go/ees005
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
32
|
+
emit, onErrorHandled) {
|
|
33
|
+
(0, _classCallCheck2.default)(this, CommitStepService);
|
|
34
|
+
this.broadcast = broadcast;
|
|
35
|
+
this.analyticsHelper = analyticsHelper;
|
|
36
|
+
this.emit = emit;
|
|
37
|
+
this.onErrorHandled = onErrorHandled;
|
|
38
|
+
this.readyToCommit = true;
|
|
39
|
+
this.lastBroadcastRequestAcked = true;
|
|
50
40
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
41
|
+
return (0, _createClass2.default)(CommitStepService, [{
|
|
42
|
+
key: "commitStepQueue",
|
|
43
|
+
value: function commitStepQueue(_ref) {
|
|
44
|
+
var _this = this;
|
|
45
|
+
var steps = _ref.steps,
|
|
46
|
+
version = _ref.version,
|
|
47
|
+
userId = _ref.userId,
|
|
48
|
+
clientId = _ref.clientId,
|
|
49
|
+
onStepsAdded = _ref.onStepsAdded,
|
|
50
|
+
__livePage = _ref.__livePage,
|
|
51
|
+
hasRecovered = _ref.hasRecovered,
|
|
52
|
+
collabMode = _ref.collabMode,
|
|
53
|
+
reason = _ref.reason,
|
|
54
|
+
numberOfStepCommitsSent = _ref.numberOfStepCommitsSent,
|
|
55
|
+
setNumberOfCommitsSent = _ref.setNumberOfCommitsSent;
|
|
56
|
+
// this timer is for waiting to send the next batch in between acks from the BE
|
|
57
|
+
var commitWaitTimer;
|
|
58
|
+
// if publishing and not waiting for an ACK, then clear the commit timer and proceed, skipping the timer
|
|
59
|
+
if (reason === 'publish' && this.lastBroadcastRequestAcked) {
|
|
60
|
+
if ((0, _platformFeatureFlags.fg)('skip_collab_provider_delay_on_publish')) {
|
|
61
|
+
clearTimeout(commitWaitTimer);
|
|
62
|
+
this.readyToCommit = true;
|
|
63
|
+
} // no-op if fg is turned off
|
|
64
|
+
}
|
|
65
|
+
if (!this.readyToCommit) {
|
|
66
|
+
logger('Not ready to commit, skip');
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
// Block other sending request, before ACK
|
|
70
|
+
this.readyToCommit = false;
|
|
71
|
+
this.lastBroadcastRequestAcked = false;
|
|
54
72
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
73
|
+
// this timer is a fallback for if an ACK from BE is lost - stop the queue from getting indefinitely locked
|
|
74
|
+
var fallbackTimer = setTimeout(function () {
|
|
75
|
+
_this.readyToCommit = true;
|
|
76
|
+
_this.lastBroadcastRequestAcked = true;
|
|
77
|
+
logger('reset readyToCommit by timer');
|
|
78
|
+
}, RESET_READYTOCOMMIT_INTERVAL_MS);
|
|
79
|
+
var stepsWithClientAndUserId = steps.map(function (step) {
|
|
80
|
+
return _objectSpread(_objectSpread({}, step.toJSON()), {}, {
|
|
81
|
+
clientId: clientId,
|
|
82
|
+
userId: userId
|
|
83
|
+
});
|
|
84
|
+
});
|
|
67
85
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
86
|
+
// Mutate steps to ignore expand expand/collapse changes in live pages
|
|
87
|
+
// This is expected to be a temporary divergence from standard editor behaviour
|
|
88
|
+
// and will be removed in Q4 when editor and live view pages align on a single
|
|
89
|
+
// behaviour for expands.
|
|
90
|
+
// While not recommended by the Editor content services team, this has been discussed
|
|
91
|
+
// as being low risk as the expand change via a setAttrs step;
|
|
92
|
+
// - doesn't impact any indexes,
|
|
93
|
+
// - is setup for last write wins,
|
|
94
|
+
// - and is just a boolean -- so no real risk of data loss.
|
|
95
|
+
if (__livePage) {
|
|
96
|
+
stepsWithClientAndUserId = stepsWithClientAndUserId.map(function (step) {
|
|
97
|
+
if (_this.isExpandChangeStep(step)) {
|
|
98
|
+
// The title is also updated via this step, which we do want to send to the server.
|
|
99
|
+
// so we strip out the __expanded attribute from the step.
|
|
100
|
+
return _objectSpread(_objectSpread({}, step), {}, {
|
|
101
|
+
attrs: {
|
|
102
|
+
title: step.attrs.title
|
|
103
|
+
}
|
|
104
|
+
});
|
|
85
105
|
}
|
|
106
|
+
return step;
|
|
86
107
|
});
|
|
87
108
|
}
|
|
88
|
-
return step;
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
109
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
110
|
+
// tag unconfirmed steps sent after page has been recovered during client's editing session
|
|
111
|
+
if (hasRecovered) {
|
|
112
|
+
stepsWithClientAndUserId = stepsWithClientAndUserId.map(function (step) {
|
|
113
|
+
step.metadata = _objectSpread(_objectSpread({}, step.metadata), {}, {
|
|
114
|
+
unconfirmedStepAfterRecovery: true
|
|
115
|
+
});
|
|
116
|
+
return step;
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
var start = new Date().getTime();
|
|
120
|
+
var ADD_STEPS_ACKNOWLEDGEMENT_ERROR_MSG = 'Error while adding steps - Invalid Acknowledgement';
|
|
121
|
+
var ADD_STEPS_BROADCAST_ERROR_MSG = 'Error while adding steps - Broadcast threw exception';
|
|
122
|
+
this.emit('commit-status', {
|
|
123
|
+
status: 'attempt',
|
|
124
|
+
version: version
|
|
97
125
|
});
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
version: version
|
|
107
|
-
});
|
|
108
|
-
try {
|
|
109
|
-
var _FeatureGates$getExpe;
|
|
110
|
-
var n = (_FeatureGates$getExpe = _featureGateJsClient.default.getExperimentValue('platform_editor_step_validation_on_connect', 'steps', 0)) !== null && _FeatureGates$getExpe !== void 0 ? _FeatureGates$getExpe : 0;
|
|
111
|
-
var isExperimentEnabled = n > 0;
|
|
112
|
-
// skip validation if FG is on and we have already sent n steps, or if FG is off
|
|
113
|
-
var skipValidation = isExperimentEnabled ? numberOfStepCommitsSent >= n : true;
|
|
114
|
-
broadcast('steps:commit', {
|
|
115
|
-
collabMode: collabMode,
|
|
116
|
-
steps: stepsWithClientAndUserId,
|
|
117
|
-
version: version,
|
|
118
|
-
userId: userId,
|
|
119
|
-
skipValidation: skipValidation
|
|
120
|
-
}, function (response) {
|
|
121
|
-
exports.lastBroadcastRequestAcked = lastBroadcastRequestAcked = true;
|
|
122
|
-
var latency = new Date().getTime() - start;
|
|
123
|
-
// this most closely replicates the BE ack delay behaviour. 500ms hardcoded + 180ms network delay (tested on hello)
|
|
124
|
-
// more context: https://hello.atlassian.net/wiki/spaces/CEPS/pages/5020556010/Spike+Moving+BE+delay+to+the+FE
|
|
125
|
-
// to be switched over to backpressure delay sent from the BE in https://hello.jira.atlassian.cloud/browse/CEPS-1030
|
|
126
|
-
var delay = latency < 680 ? 680 - latency : 1;
|
|
127
|
-
if (response.delay) {
|
|
128
|
-
delay = response.delay;
|
|
129
|
-
} // if backpressure delay is sent, overwrite it
|
|
130
|
-
|
|
131
|
-
clearTimeout(fallbackTimer); // clear the fallback timer, ack was successfully sent/recieved
|
|
132
|
-
commitWaitTimer = setTimeout(function () {
|
|
133
|
-
// unlock the queue after waiting for delay
|
|
134
|
-
exports.readyToCommit = readyToCommit = true;
|
|
135
|
-
logger('reset readyToCommit');
|
|
136
|
-
}, delay);
|
|
137
|
-
if (response.type === _types.AcknowledgementResponseTypes.SUCCESS) {
|
|
138
|
-
onStepsAdded({
|
|
126
|
+
try {
|
|
127
|
+
var _FeatureGates$getExpe;
|
|
128
|
+
var n = (_FeatureGates$getExpe = _featureGateJsClient.default.getExperimentValue('platform_editor_step_validation_on_connect', 'steps', 0)) !== null && _FeatureGates$getExpe !== void 0 ? _FeatureGates$getExpe : 0;
|
|
129
|
+
var isExperimentEnabled = n > 0;
|
|
130
|
+
// skip validation if FG is on and we have already sent n steps, or if FG is off
|
|
131
|
+
var skipValidation = isExperimentEnabled ? numberOfStepCommitsSent >= n : true;
|
|
132
|
+
this.broadcast('steps:commit', {
|
|
133
|
+
collabMode: collabMode,
|
|
139
134
|
steps: stepsWithClientAndUserId,
|
|
140
|
-
version:
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
135
|
+
version: version,
|
|
136
|
+
userId: userId,
|
|
137
|
+
skipValidation: skipValidation
|
|
138
|
+
}, function (response) {
|
|
139
|
+
_this.lastBroadcastRequestAcked = true;
|
|
140
|
+
var latency = new Date().getTime() - start;
|
|
141
|
+
// this most closely replicates the BE ack delay behaviour. 500ms hardcoded + 180ms network delay (tested on hello)
|
|
142
|
+
// more context: https://hello.atlassian.net/wiki/spaces/CEPS/pages/5020556010/Spike+Moving+BE+delay+to+the+FE
|
|
143
|
+
// to be switched over to backpressure delay sent from the BE in https://hello.jira.atlassian.cloud/browse/CEPS-1030
|
|
144
|
+
var delay = latency < 680 ? 680 - latency : 1;
|
|
145
|
+
if (response.delay) {
|
|
146
|
+
delay = response.delay;
|
|
147
|
+
} // if backpressure delay is sent, overwrite it
|
|
148
|
+
|
|
149
|
+
clearTimeout(fallbackTimer); // clear the fallback timer, ack was successfully sent/recieved
|
|
150
|
+
commitWaitTimer = setTimeout(function () {
|
|
151
|
+
// unlock the queue after waiting for delay
|
|
152
|
+
_this.readyToCommit = true;
|
|
153
|
+
logger('reset readyToCommit');
|
|
154
|
+
}, delay);
|
|
155
|
+
if (response.type === _types.AcknowledgementResponseTypes.SUCCESS) {
|
|
156
|
+
onStepsAdded({
|
|
157
|
+
steps: stepsWithClientAndUserId,
|
|
158
|
+
version: response.version
|
|
159
|
+
});
|
|
160
|
+
_this.sendSuccessAnalytics(latency, stepsWithClientAndUserId);
|
|
161
|
+
_this.emit('commit-status', {
|
|
162
|
+
status: 'success',
|
|
163
|
+
version: response.version
|
|
164
|
+
});
|
|
165
|
+
} else if (response.type === _types.AcknowledgementResponseTypes.ERROR) {
|
|
166
|
+
_this.onErrorHandled(response.error);
|
|
167
|
+
_this.sendFailureAnalytics(response, latency);
|
|
168
|
+
_this.emit('commit-status', {
|
|
169
|
+
status: 'failure',
|
|
170
|
+
version: version
|
|
171
|
+
});
|
|
172
|
+
// eslint-disable-next-line no-console
|
|
173
|
+
console.error(ADD_STEPS_ACKNOWLEDGEMENT_ERROR_MSG);
|
|
174
|
+
} else {
|
|
175
|
+
var _this$analyticsHelper;
|
|
176
|
+
(_this$analyticsHelper = _this.analyticsHelper) === null || _this$analyticsHelper === void 0 || _this$analyticsHelper.sendErrorEvent(
|
|
177
|
+
// @ts-expect-error We didn't type the invalid type case
|
|
178
|
+
new Error("Response type: ".concat((response === null || response === void 0 ? void 0 : response.type) || 'No response type')), ADD_STEPS_ACKNOWLEDGEMENT_ERROR_MSG);
|
|
179
|
+
_this.emit('commit-status', {
|
|
180
|
+
status: 'failure',
|
|
181
|
+
version: version
|
|
182
|
+
});
|
|
183
|
+
// eslint-disable-next-line no-console
|
|
184
|
+
console.error(ADD_STEPS_ACKNOWLEDGEMENT_ERROR_MSG);
|
|
185
|
+
}
|
|
153
186
|
});
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
187
|
+
if (isExperimentEnabled && numberOfStepCommitsSent < n) {
|
|
188
|
+
setNumberOfCommitsSent(numberOfStepCommitsSent + 1);
|
|
189
|
+
}
|
|
190
|
+
} catch (error) {
|
|
191
|
+
var _this$analyticsHelper2;
|
|
192
|
+
// if the broadcast failed for any reason, we shouldn't keep the queue locked as the BE has not recieved any message
|
|
193
|
+
this.readyToCommit = true;
|
|
194
|
+
(_this$analyticsHelper2 = this.analyticsHelper) === null || _this$analyticsHelper2 === void 0 || _this$analyticsHelper2.sendErrorEvent(error, ADD_STEPS_BROADCAST_ERROR_MSG);
|
|
195
|
+
this.emit('commit-status', {
|
|
161
196
|
status: 'failure',
|
|
162
197
|
version: version
|
|
163
198
|
});
|
|
164
199
|
// eslint-disable-next-line no-console
|
|
165
|
-
console.error(
|
|
200
|
+
console.error(ADD_STEPS_BROADCAST_ERROR_MSG);
|
|
166
201
|
}
|
|
167
|
-
});
|
|
168
|
-
if (isExperimentEnabled && numberOfStepCommitsSent < n) {
|
|
169
|
-
setNumberOfCommitsSent(numberOfStepCommitsSent + 1);
|
|
170
202
|
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
analyticsHelper === null || analyticsHelper === void 0 || analyticsHelper.sendErrorEvent(error, ADD_STEPS_BROADCAST_ERROR_MSG);
|
|
175
|
-
emit('commit-status', {
|
|
176
|
-
status: 'failure',
|
|
177
|
-
version: version
|
|
178
|
-
});
|
|
179
|
-
// eslint-disable-next-line no-console
|
|
180
|
-
console.error(ADD_STEPS_BROADCAST_ERROR_MSG);
|
|
181
|
-
}
|
|
182
|
-
};
|
|
183
|
-
function isExpandChangeStep(step) {
|
|
184
|
-
// Ignored via go/ees005
|
|
185
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
186
|
-
if (step.stepType === 'setAttrs' && '__expanded' in step.attrs) {
|
|
187
|
-
return true;
|
|
188
|
-
}
|
|
189
|
-
return false;
|
|
190
|
-
}
|
|
191
|
-
function sendSuccessAnalytics(latency, stepsWithClientAndUserId, analyticsHelper) {
|
|
192
|
-
// Sample only 10% of add steps events to avoid overwhelming the analytics
|
|
193
|
-
if (Math.random() < 0.1) {
|
|
194
|
-
analyticsHelper === null || analyticsHelper === void 0 || analyticsHelper.sendActionEvent(_const.EVENT_ACTION.ADD_STEPS, _const.EVENT_STATUS.SUCCESS_10x_SAMPLED, {
|
|
195
|
-
type: _const.ADD_STEPS_TYPE.ACCEPTED,
|
|
196
|
-
latency: latency,
|
|
197
|
-
stepType: (0, _countBy.default)(stepsWithClientAndUserId,
|
|
203
|
+
}, {
|
|
204
|
+
key: "isExpandChangeStep",
|
|
205
|
+
value: function isExpandChangeStep(step) {
|
|
198
206
|
// Ignored via go/ees005
|
|
199
|
-
// eslint-disable-next-line @typescript-eslint/no-
|
|
200
|
-
|
|
201
|
-
return
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
207
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
208
|
+
if (step.stepType === 'setAttrs' && '__expanded' in step.attrs) {
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
}, {
|
|
214
|
+
key: "sendSuccessAnalytics",
|
|
215
|
+
value: function sendSuccessAnalytics(latency, stepsWithClientAndUserId) {
|
|
216
|
+
// Sample only 10% of add steps events to avoid overwhelming the analytics
|
|
217
|
+
if (Math.random() < 0.1) {
|
|
218
|
+
var _this$analyticsHelper3;
|
|
219
|
+
(_this$analyticsHelper3 = this.analyticsHelper) === null || _this$analyticsHelper3 === void 0 || _this$analyticsHelper3.sendActionEvent(_const.EVENT_ACTION.ADD_STEPS, _const.EVENT_STATUS.SUCCESS_10x_SAMPLED, {
|
|
220
|
+
type: _const.ADD_STEPS_TYPE.ACCEPTED,
|
|
221
|
+
latency: latency,
|
|
222
|
+
stepType: (0, _countBy.default)(stepsWithClientAndUserId,
|
|
223
|
+
// Ignored via go/ees005
|
|
224
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
225
|
+
function (stepWithClientAndUserId) {
|
|
226
|
+
return stepWithClientAndUserId.stepType;
|
|
227
|
+
})
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}, {
|
|
232
|
+
key: "sendFailureAnalytics",
|
|
233
|
+
value: function sendFailureAnalytics(response, latency) {
|
|
234
|
+
var _this$analyticsHelper4, _this$analyticsHelper5;
|
|
235
|
+
(_this$analyticsHelper4 = this.analyticsHelper) === null || _this$analyticsHelper4 === void 0 || _this$analyticsHelper4.sendActionEvent(_const.EVENT_ACTION.ADD_STEPS, _const.EVENT_STATUS.FAILURE, {
|
|
236
|
+
// User tried committing steps but they were rejected because:
|
|
237
|
+
// - HEAD_VERSION_UPDATE_FAILED: the collab service's latest stored step tail version didn't correspond to the head version of the first step submitted
|
|
238
|
+
// - VERSION_NUMBER_ALREADY_EXISTS: while storing the steps there was a conflict meaning someone else wrote steps into the database more quickly
|
|
239
|
+
type: response.error.data.code === _ncsErrors.NCS_ERROR_CODE.HEAD_VERSION_UPDATE_FAILED || response.error.data.code === _ncsErrors.NCS_ERROR_CODE.VERSION_NUMBER_ALREADY_EXISTS ? _const.ADD_STEPS_TYPE.REJECTED : _const.ADD_STEPS_TYPE.ERROR,
|
|
240
|
+
latency: latency
|
|
241
|
+
});
|
|
242
|
+
(_this$analyticsHelper5 = this.analyticsHelper) === null || _this$analyticsHelper5 === void 0 || _this$analyticsHelper5.sendErrorEvent(response.error, 'Error while adding steps - Acknowledgement Error');
|
|
243
|
+
}
|
|
244
|
+
}, {
|
|
245
|
+
key: "getReadyToCommitStatus",
|
|
246
|
+
value: function getReadyToCommitStatus() {
|
|
247
|
+
return this.readyToCommit;
|
|
248
|
+
}
|
|
249
|
+
}]);
|
|
250
|
+
}();
|
|
@@ -5,7 +5,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.version = exports.nextMajorVersion = exports.name = void 0;
|
|
7
7
|
var name = exports.name = "@atlaskit/collab-provider";
|
|
8
|
-
var version = exports.version = "10.20.
|
|
8
|
+
var version = exports.version = "10.20.2";
|
|
9
9
|
var nextMajorVersion = exports.nextMajorVersion = function nextMajorVersion() {
|
|
10
10
|
return [Number(version.split('.')[0]) + 1, 0, 0].join('.');
|
|
11
11
|
};
|
|
@@ -5,12 +5,14 @@ import { getCollabState, sendableSteps } from '@atlaskit/prosemirror-collab';
|
|
|
5
5
|
import { Transaction } from '@atlaskit/editor-prosemirror/state';
|
|
6
6
|
import { JSONTransformer } from '@atlaskit/editor-json-transformer';
|
|
7
7
|
import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
|
|
8
|
+
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
|
|
8
9
|
import { ACK_MAX_TRY, EVENT_ACTION, EVENT_STATUS, CatchupEventReason } from '../helpers/const';
|
|
9
10
|
import { MEASURE_NAME, startMeasure, stopMeasure } from '../analytics/performance';
|
|
10
11
|
import { INTERNAL_ERROR_CODE } from '../errors/internal-errors';
|
|
11
12
|
import { createLogger, getDocAdfWithObfuscationFromJSON, getObfuscatedSteps, getStepUGCFreeDetails, sleep } from '../helpers/utils';
|
|
13
|
+
import { SINGLE_COLLAB_MODE } from '../participants/participants-service';
|
|
12
14
|
import { MAX_STEP_REJECTED_ERROR, MAX_STEP_REJECTED_ERROR_AGGRESSIVE } from '../provider';
|
|
13
|
-
import {
|
|
15
|
+
import { CommitStepService } from '../provider/commit-step';
|
|
14
16
|
import { CantSyncUpError, UpdateDocumentError } from '../errors/custom-errors';
|
|
15
17
|
import { catchupv2 } from './catchupv2';
|
|
16
18
|
import { StepQueueState } from './step-queue-state';
|
|
@@ -694,6 +696,7 @@ export class DocumentService {
|
|
|
694
696
|
this.getConnected = getConnected;
|
|
695
697
|
this.stepQueue = new StepQueueState();
|
|
696
698
|
this.onErrorHandled = onErrorHandled;
|
|
699
|
+
this.commitStepService = new CommitStepService(this.broadcast, this.analyticsHelper, this.providerEmitCallback, this.onErrorHandled);
|
|
697
700
|
}
|
|
698
701
|
getVersionFromCollabState(state, resource) {
|
|
699
702
|
const collabState = getCollabState(state);
|
|
@@ -867,8 +870,9 @@ export class DocumentService {
|
|
|
867
870
|
const version = this.getVersionFromCollabState(newState, 'collab-provider: send');
|
|
868
871
|
|
|
869
872
|
// Don't send any steps before we're ready.
|
|
870
|
-
if (editorExperiment('platform_editor_offline_editing_web', true)) {
|
|
871
|
-
|
|
873
|
+
if (editorExperiment('platform_editor_offline_editing_web', true) || expValEquals('platform_editor_enable_single_player_step_merging', 'isEnabled', true)) {
|
|
874
|
+
const enableStepsMergingForSinglePlayer = expValEquals('platform_editor_enable_single_player_step_merging', 'isEnabled', true) && !this.commitStepService.getReadyToCommitStatus() && this.participantsService.getCollabMode() === SINGLE_COLLAB_MODE;
|
|
875
|
+
if (!unconfirmedStepsData || !this.getConnected() || enableStepsMergingForSinglePlayer) {
|
|
872
876
|
return;
|
|
873
877
|
}
|
|
874
878
|
} else {
|
|
@@ -910,7 +914,7 @@ export class DocumentService {
|
|
|
910
914
|
// If we are going to commit unconfirmed steps
|
|
911
915
|
// we need to lock them to ensure they don't get
|
|
912
916
|
// mutated in: `packages/editor/editor-plugin-collab-edit/src/pm-plugins/mergeUnconfirmed.ts`
|
|
913
|
-
if (editorExperiment('platform_editor_offline_editing_web', true)) {
|
|
917
|
+
if (editorExperiment('platform_editor_offline_editing_web', true) || expValEquals('platform_editor_enable_single_player_step_merging', 'isEnabled', true)) {
|
|
914
918
|
unconfirmedStepsData.origins.forEach(origin => {
|
|
915
919
|
if (origin instanceof Transaction) {
|
|
916
920
|
return origin.setMeta('mergeIsLocked', true);
|
|
@@ -928,8 +932,7 @@ export class DocumentService {
|
|
|
928
932
|
// Avoid reference issues using a
|
|
929
933
|
// method outside of the provider
|
|
930
934
|
// scope
|
|
931
|
-
commitStepQueue({
|
|
932
|
-
broadcast: this.broadcast,
|
|
935
|
+
this.commitStepService.commitStepQueue({
|
|
933
936
|
// Ignored via go/ees005
|
|
934
937
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
935
938
|
userId: this.getUserId(),
|
|
@@ -939,9 +942,6 @@ export class DocumentService {
|
|
|
939
942
|
steps: unconfirmedSteps,
|
|
940
943
|
version,
|
|
941
944
|
onStepsAdded: this.onStepsAdded,
|
|
942
|
-
onErrorHandled: this.onErrorHandled,
|
|
943
|
-
analyticsHelper: this.analyticsHelper,
|
|
944
|
-
emit: this.providerEmitCallback,
|
|
945
945
|
__livePage: this.options.__livePage,
|
|
946
946
|
hasRecovered: this.hasRecovered,
|
|
947
947
|
collabMode: this.participantsService.getCollabMode(),
|
|
@@ -9,6 +9,8 @@ const logger = createLogger('PresenceService', 'pink');
|
|
|
9
9
|
const SEND_PRESENCE_INTERVAL = 150 * 1000; // 150 seconds
|
|
10
10
|
const DEFAULT_FETCH_USERS_INTERVAL = 500; // 0.5 second
|
|
11
11
|
const UNIDENTIFIED = 'unidentified';
|
|
12
|
+
export const SINGLE_COLLAB_MODE = 'single';
|
|
13
|
+
export const MULTI_COLLAB_MODE = 'collab';
|
|
12
14
|
|
|
13
15
|
/**
|
|
14
16
|
* This service is responsible for handling presence and participant events, as well as sending them on to the editor or NCS.
|
|
@@ -532,7 +534,7 @@ export class ParticipantsService {
|
|
|
532
534
|
return this.participantsState.getAIProviderParticipants();
|
|
533
535
|
});
|
|
534
536
|
_defineProperty(this, "getCollabMode", () => {
|
|
535
|
-
return this.participantsState.size() > 1 ?
|
|
537
|
+
return this.participantsState.size() > 1 ? MULTI_COLLAB_MODE : SINGLE_COLLAB_MODE;
|
|
536
538
|
});
|
|
537
539
|
this.analyticsHelper = analyticsHelper;
|
|
538
540
|
this.participantsState = participantsState;
|