@atlaskit/collab-provider 10.9.2 → 10.9.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/cjs/channel.js +1 -1
  3. package/dist/cjs/document/document-service.js +180 -156
  4. package/dist/cjs/document/getConflictChanges.js +177 -0
  5. package/dist/cjs/document/null-document-service.js +1 -1
  6. package/dist/cjs/errors/ncs-errors.js +1 -1
  7. package/dist/cjs/provider/commit-step.js +3 -4
  8. package/dist/cjs/provider/index.js +27 -22
  9. package/dist/cjs/version-wrapper.js +1 -1
  10. package/dist/es2019/channel.js +1 -1
  11. package/dist/es2019/document/document-service.js +27 -12
  12. package/dist/es2019/document/getConflictChanges.js +161 -0
  13. package/dist/es2019/document/null-document-service.js +1 -1
  14. package/dist/es2019/errors/ncs-errors.js +1 -1
  15. package/dist/es2019/provider/commit-step.js +3 -4
  16. package/dist/es2019/provider/index.js +3 -3
  17. package/dist/es2019/version-wrapper.js +1 -1
  18. package/dist/esm/channel.js +1 -1
  19. package/dist/esm/document/document-service.js +180 -156
  20. package/dist/esm/document/getConflictChanges.js +170 -0
  21. package/dist/esm/document/null-document-service.js +1 -1
  22. package/dist/esm/errors/ncs-errors.js +1 -1
  23. package/dist/esm/provider/commit-step.js +3 -4
  24. package/dist/esm/provider/index.js +27 -22
  25. package/dist/esm/version-wrapper.js +1 -1
  26. package/dist/types/document/document-service.d.ts +5 -4
  27. package/dist/types/document/getConflictChanges.d.ts +26 -0
  28. package/dist/types/document/interface-document-service.d.ts +3 -2
  29. package/dist/types/document/null-document-service.d.ts +2 -1
  30. package/dist/types/provider/commit-step.d.ts +4 -3
  31. package/dist/types/provider/index.d.ts +2 -1
  32. package/dist/types-ts4.5/document/document-service.d.ts +5 -4
  33. package/dist/types-ts4.5/document/getConflictChanges.d.ts +26 -0
  34. package/dist/types-ts4.5/document/interface-document-service.d.ts +3 -2
  35. package/dist/types-ts4.5/document/null-document-service.d.ts +2 -1
  36. package/dist/types-ts4.5/provider/commit-step.d.ts +4 -3
  37. package/dist/types-ts4.5/provider/index.d.ts +2 -1
  38. package/package.json +5 -4
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # @atlaskit/collab-provider
2
2
 
3
+ ## 10.9.4
4
+
5
+ ### Patch Changes
6
+
7
+ - [#124114](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/pull-requests/124114)
8
+ [`a0b9383dc1bf3`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/a0b9383dc1bf3) -
9
+ CEPS-362: add reason to getResolvedEditorState call chain, to allow collab provider/NCS to
10
+ differentiate between draft sync and publish use cases
11
+ - Updated dependencies
12
+
13
+ ## 10.9.3
14
+
15
+ ### Patch Changes
16
+
17
+ - [#122605](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/pull-requests/122605)
18
+ [`1bf1493f744ce`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/1bf1493f744ce) -
19
+ [ux] Add conflict metadata on reconnection
20
+ - Updated dependencies
21
+
3
22
  ## 10.9.2
4
23
 
5
24
  ### Patch Changes
@@ -203,7 +203,7 @@ var Channel = exports.Channel = /*#__PURE__*/function (_Emitter) {
203
203
  var measure = (0, _performance.stopMeasure)(_performance.MEASURE_NAME.DOCUMENT_INIT, _this.analyticsHelper);
204
204
  (_this$initExperience = _this.initExperience) === null || _this$initExperience === void 0 || _this$initExperience.success();
205
205
  (_this$analyticsHelper6 = _this.analyticsHelper) === null || _this$analyticsHelper6 === void 0 || _this$analyticsHelper6.sendActionEvent(_const.EVENT_ACTION.DOCUMENT_INIT,
206
- // TODO: detect when document init fails and fire corresponding event for it
206
+ // TODO: ED-26957 - detect when document init fails and fire corresponding event for it
207
207
  _const.EVENT_STATUS.SUCCESS, {
208
208
  latency: measure === null || measure === void 0 ? void 0 : measure.duration,
209
209
  resetReason: data.resetReason,
@@ -12,6 +12,7 @@ var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/creat
12
12
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
13
13
  var _throttle = _interopRequireDefault(require("lodash/throttle"));
14
14
  var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
15
+ var _transform = require("@atlaskit/editor-prosemirror/transform");
15
16
  var _prosemirrorCollab = require("@atlaskit/prosemirror-collab");
16
17
  var _state = require("@atlaskit/editor-prosemirror/state");
17
18
  var _editorJsonTransformer = require("@atlaskit/editor-json-transformer");
@@ -24,6 +25,7 @@ var _commitStep = require("../provider/commit-step");
24
25
  var _customErrors = require("../errors/custom-errors");
25
26
  var _catchupv = require("./catchupv2");
26
27
  var _stepQueueState = require("./step-queue-state");
28
+ var _getConflictChanges = require("./getConflictChanges");
27
29
  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; }
28
30
  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; }
29
31
  var CATCHUP_THROTTLE = 1 * 1000; // 1 second
@@ -72,7 +74,7 @@ var DocumentService = exports.DocumentService = /*#__PURE__*/function () {
72
74
  return _this.catchupv2(reason, reconnectionMetadata);
73
75
  }, CATCHUP_THROTTLE, {
74
76
  leading: false,
75
- // TODO: why shouldn't this be leading?
77
+ // TODO: ED-26957 - why shouldn't this be leading?
76
78
  trailing: true
77
79
  }));
78
80
  /**
@@ -458,63 +460,68 @@ var DocumentService = exports.DocumentService = /*#__PURE__*/function () {
458
460
  return _ref7.apply(this, arguments);
459
461
  };
460
462
  }());
461
- (0, _defineProperty2.default)(this, "getFinalAcknowledgedState", /*#__PURE__*/(0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee4() {
462
- var _this$analyticsHelper18, finalAcknowledgedState, currentState, reconcileResponse, measure, _this$analyticsHelper19, _this$analyticsHelper20, _measure2;
463
- return _regenerator.default.wrap(function _callee4$(_context4) {
464
- while (1) switch (_context4.prev = _context4.next) {
465
- case 0:
466
- _this.aggressiveCatchup = true;
467
- _context4.prev = 1;
468
- (0, _performance.startMeasure)(_performance.MEASURE_NAME.PUBLISH_PAGE, _this.analyticsHelper);
469
- _context4.prev = 3;
470
- _context4.next = 6;
471
- return _this.commitUnconfirmedSteps();
472
- case 6:
473
- _context4.next = 8;
474
- return _this.getCurrentState();
475
- case 8:
476
- finalAcknowledgedState = _context4.sent;
477
- _context4.next = 20;
478
- break;
479
- case 11:
480
- _context4.prev = 11;
481
- _context4.t0 = _context4["catch"](3);
482
- _context4.next = 15;
483
- return _this.getCurrentState();
484
- case 15:
485
- currentState = _context4.sent;
486
- _context4.next = 18;
487
- return _this.fetchReconcile(JSON.stringify(currentState.content), 'fe-final-ack');
488
- case 18:
489
- reconcileResponse = _context4.sent;
490
- finalAcknowledgedState = {
491
- content: JSON.parse(reconcileResponse.document),
492
- title: currentState.title,
493
- stepVersion: reconcileResponse.version
494
- };
495
- case 20:
496
- measure = (0, _performance.stopMeasure)(_performance.MEASURE_NAME.PUBLISH_PAGE, _this.analyticsHelper);
497
- (_this$analyticsHelper18 = _this.analyticsHelper) === null || _this$analyticsHelper18 === void 0 || _this$analyticsHelper18.sendActionEvent(_const.EVENT_ACTION.PUBLISH_PAGE, _const.EVENT_STATUS.SUCCESS, {
498
- latency: measure === null || measure === void 0 ? void 0 : measure.duration
499
- });
500
- _this.aggressiveCatchup = false;
501
- return _context4.abrupt("return", finalAcknowledgedState);
502
- case 26:
503
- _context4.prev = 26;
504
- _context4.t1 = _context4["catch"](1);
505
- _this.aggressiveCatchup = false;
506
- _measure2 = (0, _performance.stopMeasure)(_performance.MEASURE_NAME.PUBLISH_PAGE, _this.analyticsHelper);
507
- (_this$analyticsHelper19 = _this.analyticsHelper) === null || _this$analyticsHelper19 === void 0 || _this$analyticsHelper19.sendActionEvent(_const.EVENT_ACTION.PUBLISH_PAGE, _const.EVENT_STATUS.FAILURE, {
508
- latency: _measure2 === null || _measure2 === void 0 ? void 0 : _measure2.duration
509
- });
510
- (_this$analyticsHelper20 = _this.analyticsHelper) === null || _this$analyticsHelper20 === void 0 || _this$analyticsHelper20.sendErrorEvent(_context4.t1, 'Error while returning ADF version of the final draft document');
511
- throw _context4.t1;
512
- case 33:
513
- case "end":
514
- return _context4.stop();
515
- }
516
- }, _callee4, null, [[1, 26], [3, 11]]);
517
- })));
463
+ (0, _defineProperty2.default)(this, "getFinalAcknowledgedState", /*#__PURE__*/function () {
464
+ var _ref8 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee4(reason) {
465
+ var _this$analyticsHelper18, finalAcknowledgedState, currentState, reconcileResponse, measure, _this$analyticsHelper19, _this$analyticsHelper20, _measure2;
466
+ return _regenerator.default.wrap(function _callee4$(_context4) {
467
+ while (1) switch (_context4.prev = _context4.next) {
468
+ case 0:
469
+ _this.aggressiveCatchup = true;
470
+ _context4.prev = 1;
471
+ (0, _performance.startMeasure)(_performance.MEASURE_NAME.PUBLISH_PAGE, _this.analyticsHelper);
472
+ _context4.prev = 3;
473
+ _context4.next = 6;
474
+ return _this.commitUnconfirmedSteps(reason);
475
+ case 6:
476
+ _context4.next = 8;
477
+ return _this.getCurrentState();
478
+ case 8:
479
+ finalAcknowledgedState = _context4.sent;
480
+ _context4.next = 20;
481
+ break;
482
+ case 11:
483
+ _context4.prev = 11;
484
+ _context4.t0 = _context4["catch"](3);
485
+ _context4.next = 15;
486
+ return _this.getCurrentState();
487
+ case 15:
488
+ currentState = _context4.sent;
489
+ _context4.next = 18;
490
+ return _this.fetchReconcile(JSON.stringify(currentState.content), 'fe-final-ack');
491
+ case 18:
492
+ reconcileResponse = _context4.sent;
493
+ finalAcknowledgedState = {
494
+ content: JSON.parse(reconcileResponse.document),
495
+ title: currentState.title,
496
+ stepVersion: reconcileResponse.version
497
+ };
498
+ case 20:
499
+ measure = (0, _performance.stopMeasure)(_performance.MEASURE_NAME.PUBLISH_PAGE, _this.analyticsHelper);
500
+ (_this$analyticsHelper18 = _this.analyticsHelper) === null || _this$analyticsHelper18 === void 0 || _this$analyticsHelper18.sendActionEvent(_const.EVENT_ACTION.PUBLISH_PAGE, _const.EVENT_STATUS.SUCCESS, {
501
+ latency: measure === null || measure === void 0 ? void 0 : measure.duration
502
+ });
503
+ _this.aggressiveCatchup = false;
504
+ return _context4.abrupt("return", finalAcknowledgedState);
505
+ case 26:
506
+ _context4.prev = 26;
507
+ _context4.t1 = _context4["catch"](1);
508
+ _this.aggressiveCatchup = false;
509
+ _measure2 = (0, _performance.stopMeasure)(_performance.MEASURE_NAME.PUBLISH_PAGE, _this.analyticsHelper);
510
+ (_this$analyticsHelper19 = _this.analyticsHelper) === null || _this$analyticsHelper19 === void 0 || _this$analyticsHelper19.sendActionEvent(_const.EVENT_ACTION.PUBLISH_PAGE, _const.EVENT_STATUS.FAILURE, {
511
+ latency: _measure2 === null || _measure2 === void 0 ? void 0 : _measure2.duration
512
+ });
513
+ (_this$analyticsHelper20 = _this.analyticsHelper) === null || _this$analyticsHelper20 === void 0 || _this$analyticsHelper20.sendErrorEvent(_context4.t1, 'Error while returning ADF version of the final draft document');
514
+ throw _context4.t1;
515
+ case 33:
516
+ case "end":
517
+ return _context4.stop();
518
+ }
519
+ }, _callee4, null, [[1, 26], [3, 11]]);
520
+ }));
521
+ return function (_x4) {
522
+ return _ref8.apply(this, arguments);
523
+ };
524
+ }());
518
525
  (0, _defineProperty2.default)(this, "updateDocument", function (_ref9) {
519
526
  var doc = _ref9.doc,
520
527
  version = _ref9.version,
@@ -605,100 +612,105 @@ var DocumentService = exports.DocumentService = /*#__PURE__*/function () {
605
612
  * Commit the unconfirmed local steps to the back-end service
606
613
  * @throws {Error} Couldn't sync the steps after retrying 30 times
607
614
  */
608
- (0, _defineProperty2.default)(this, "commitUnconfirmedSteps", /*#__PURE__*/(0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee5() {
609
- var unconfirmedSteps, _this$getState6, _this$analyticsHelper25, count, unconfirmedTrs, lastTr, isLastTrConfirmed, _this$analyticsHelper24, nextUnconfirmedSteps, nextUnconfirmedTrs, _this$getUnconfirmedS, state, version, unconfirmedStepsInfoUGCRemoved, error, measure, _this$analyticsHelper26, _this$analyticsHelper27, _measure3;
610
- return _regenerator.default.wrap(function _callee5$(_context5) {
611
- while (1) switch (_context5.prev = _context5.next) {
612
- case 0:
613
- unconfirmedSteps = _this.getUnconfirmedSteps();
614
- _context5.prev = 1;
615
- if (!(unconfirmedSteps !== null && unconfirmedSteps !== void 0 && unconfirmedSteps.length)) {
616
- _context5.next = 24;
617
- break;
618
- }
619
- (0, _performance.startMeasure)(_performance.MEASURE_NAME.COMMIT_UNCONFIRMED_STEPS, _this.analyticsHelper);
620
- count = 0; // We use origins here as steps can be rebased. When steps are rebased a new step is created.
621
- // This means that we can not track if it has been removed from the unconfirmed array or not.
622
- // Origins points to the original transaction that the step was created in. This is never changed
623
- // and gets passed down when a step is rebased.
624
- unconfirmedTrs = _this.getUnconfirmedStepsOrigins();
625
- lastTr = unconfirmedTrs === null || unconfirmedTrs === void 0 ? void 0 : unconfirmedTrs[unconfirmedTrs.length - 1];
626
- isLastTrConfirmed = false;
627
- if (!((_this$getState6 = _this.getState) !== null && _this$getState6 !== void 0 && _this$getState6.call(_this))) {
628
- (_this$analyticsHelper24 = _this.analyticsHelper) === null || _this$analyticsHelper24 === void 0 || _this$analyticsHelper24.sendErrorEvent(new Error('Editor state is undefined'), 'commitUnconfirmedSteps called without state');
629
- }
630
- case 9:
631
- if (isLastTrConfirmed) {
632
- _context5.next = 22;
615
+ (0, _defineProperty2.default)(this, "commitUnconfirmedSteps", /*#__PURE__*/function () {
616
+ var _ref10 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee5(reason) {
617
+ var unconfirmedSteps, _this$getState6, _this$analyticsHelper25, count, unconfirmedTrs, lastTr, isLastTrConfirmed, _this$analyticsHelper24, nextUnconfirmedSteps, nextUnconfirmedTrs, _this$getUnconfirmedS, state, version, unconfirmedStepsInfoUGCRemoved, error, measure, _this$analyticsHelper26, _this$analyticsHelper27, _measure3;
618
+ return _regenerator.default.wrap(function _callee5$(_context5) {
619
+ while (1) switch (_context5.prev = _context5.next) {
620
+ case 0:
621
+ unconfirmedSteps = _this.getUnconfirmedSteps();
622
+ _context5.prev = 1;
623
+ if (!(unconfirmedSteps !== null && unconfirmedSteps !== void 0 && unconfirmedSteps.length)) {
624
+ _context5.next = 24;
625
+ break;
626
+ }
627
+ (0, _performance.startMeasure)(_performance.MEASURE_NAME.COMMIT_UNCONFIRMED_STEPS, _this.analyticsHelper);
628
+ count = 0; // We use origins here as steps can be rebased. When steps are rebased a new step is created.
629
+ // This means that we can not track if it has been removed from the unconfirmed array or not.
630
+ // Origins points to the original transaction that the step was created in. This is never changed
631
+ // and gets passed down when a step is rebased.
632
+ unconfirmedTrs = _this.getUnconfirmedStepsOrigins();
633
+ lastTr = unconfirmedTrs === null || unconfirmedTrs === void 0 ? void 0 : unconfirmedTrs[unconfirmedTrs.length - 1];
634
+ isLastTrConfirmed = false;
635
+ if (!((_this$getState6 = _this.getState) !== null && _this$getState6 !== void 0 && _this$getState6.call(_this))) {
636
+ (_this$analyticsHelper24 = _this.analyticsHelper) === null || _this$analyticsHelper24 === void 0 || _this$analyticsHelper24.sendErrorEvent(new Error('Editor state is undefined'), 'commitUnconfirmedSteps called without state');
637
+ }
638
+ case 9:
639
+ if (isLastTrConfirmed) {
640
+ _context5.next = 22;
641
+ break;
642
+ }
643
+ // forcePublish = true, this is because commitUnconfirmedSteps is only called when the Editor publishes a document
644
+ _this.sendStepsFromCurrentState(undefined, reason);
645
+ _context5.next = 13;
646
+ return (0, _utils.sleep)(500);
647
+ case 13:
648
+ nextUnconfirmedSteps = _this.getUnconfirmedSteps();
649
+ if (nextUnconfirmedSteps !== null && nextUnconfirmedSteps !== void 0 && nextUnconfirmedSteps.length) {
650
+ nextUnconfirmedTrs = _this.getUnconfirmedStepsOrigins();
651
+ isLastTrConfirmed = !(nextUnconfirmedTrs !== null && nextUnconfirmedTrs !== void 0 && nextUnconfirmedTrs.some(function (tr) {
652
+ return tr === lastTr;
653
+ }));
654
+ } else {
655
+ isLastTrConfirmed = true;
656
+ }
657
+ if (!(!isLastTrConfirmed && count++ >= _const.ACK_MAX_TRY)) {
658
+ _context5.next = 20;
659
+ break;
660
+ }
661
+ if (_this.onSyncUpError) {
662
+ // Ignored via go/ees005
663
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
664
+ state = _this.getState();
665
+ version = _this.getVersionFromCollabState(state, 'collab-provider: commitUnconfirmedSteps');
666
+ _this.onSyncUpError({
667
+ lengthOfUnconfirmedSteps: nextUnconfirmedSteps === null || nextUnconfirmedSteps === void 0 ? void 0 : nextUnconfirmedSteps.length,
668
+ tries: count,
669
+ maxRetries: _const.ACK_MAX_TRY,
670
+ clientId: _this.clientId,
671
+ version: version
672
+ });
673
+ }
674
+ unconfirmedStepsInfoUGCRemoved = (_this$getUnconfirmedS = _this.getUnconfirmedSteps()) === null || _this$getUnconfirmedS === void 0 ? void 0 : _this$getUnconfirmedS.map(function (step) {
675
+ return (0, _utils.getStepUGCFreeDetails)(step);
676
+ });
677
+ error = new _customErrors.CantSyncUpError("Can't sync up with Collab Service: unable to send unconfirmed steps and max retry reached", {
678
+ unconfirmedStepsInfo: unconfirmedStepsInfoUGCRemoved ? JSON.stringify(unconfirmedStepsInfoUGCRemoved) : 'Unable to generate UGC removed step info'
679
+ });
680
+ throw error;
681
+ case 20:
682
+ _context5.next = 9;
633
683
  break;
634
- }
635
- // forcePublish = true, this is because commitUnconfirmedSteps is only called when the Editor publishes a document
636
- _this.sendStepsFromCurrentState(undefined, true);
637
- _context5.next = 13;
638
- return (0, _utils.sleep)(500);
639
- case 13:
640
- nextUnconfirmedSteps = _this.getUnconfirmedSteps();
641
- if (nextUnconfirmedSteps !== null && nextUnconfirmedSteps !== void 0 && nextUnconfirmedSteps.length) {
642
- nextUnconfirmedTrs = _this.getUnconfirmedStepsOrigins();
643
- isLastTrConfirmed = !(nextUnconfirmedTrs !== null && nextUnconfirmedTrs !== void 0 && nextUnconfirmedTrs.some(function (tr) {
644
- return tr === lastTr;
645
- }));
646
- } else {
647
- isLastTrConfirmed = true;
648
- }
649
- if (!(!isLastTrConfirmed && count++ >= _const.ACK_MAX_TRY)) {
650
- _context5.next = 20;
684
+ case 22:
685
+ measure = (0, _performance.stopMeasure)(_performance.MEASURE_NAME.COMMIT_UNCONFIRMED_STEPS, _this.analyticsHelper);
686
+ (_this$analyticsHelper25 = _this.analyticsHelper) === null || _this$analyticsHelper25 === void 0 || _this$analyticsHelper25.sendActionEvent(_const.EVENT_ACTION.COMMIT_UNCONFIRMED_STEPS, _const.EVENT_STATUS.SUCCESS, {
687
+ latency: measure === null || measure === void 0 ? void 0 : measure.duration,
688
+ // upon success, emit the total number of unconfirmed steps we synced
689
+ numUnconfirmedSteps: unconfirmedSteps === null || unconfirmedSteps === void 0 ? void 0 : unconfirmedSteps.length
690
+ });
691
+ case 24:
692
+ _context5.next = 32;
651
693
  break;
652
- }
653
- if (_this.onSyncUpError) {
654
- // Ignored via go/ees005
655
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
656
- state = _this.getState();
657
- version = _this.getVersionFromCollabState(state, 'collab-provider: commitUnconfirmedSteps');
658
- _this.onSyncUpError({
659
- lengthOfUnconfirmedSteps: nextUnconfirmedSteps === null || nextUnconfirmedSteps === void 0 ? void 0 : nextUnconfirmedSteps.length,
660
- tries: count,
661
- maxRetries: _const.ACK_MAX_TRY,
662
- clientId: _this.clientId,
663
- version: version
694
+ case 26:
695
+ _context5.prev = 26;
696
+ _context5.t0 = _context5["catch"](1);
697
+ _measure3 = (0, _performance.stopMeasure)(_performance.MEASURE_NAME.COMMIT_UNCONFIRMED_STEPS, _this.analyticsHelper);
698
+ (_this$analyticsHelper26 = _this.analyticsHelper) === null || _this$analyticsHelper26 === void 0 || _this$analyticsHelper26.sendActionEvent(_const.EVENT_ACTION.COMMIT_UNCONFIRMED_STEPS, _const.EVENT_STATUS.FAILURE, {
699
+ latency: _measure3 === null || _measure3 === void 0 ? void 0 : _measure3.duration,
700
+ numUnconfirmedSteps: unconfirmedSteps === null || unconfirmedSteps === void 0 ? void 0 : unconfirmedSteps.length
664
701
  });
665
- }
666
- unconfirmedStepsInfoUGCRemoved = (_this$getUnconfirmedS = _this.getUnconfirmedSteps()) === null || _this$getUnconfirmedS === void 0 ? void 0 : _this$getUnconfirmedS.map(function (step) {
667
- return (0, _utils.getStepUGCFreeDetails)(step);
668
- });
669
- error = new _customErrors.CantSyncUpError("Can't sync up with Collab Service: unable to send unconfirmed steps and max retry reached", {
670
- unconfirmedStepsInfo: unconfirmedStepsInfoUGCRemoved ? JSON.stringify(unconfirmedStepsInfoUGCRemoved) : 'Unable to generate UGC removed step info'
671
- });
672
- throw error;
673
- case 20:
674
- _context5.next = 9;
675
- break;
676
- case 22:
677
- measure = (0, _performance.stopMeasure)(_performance.MEASURE_NAME.COMMIT_UNCONFIRMED_STEPS, _this.analyticsHelper);
678
- (_this$analyticsHelper25 = _this.analyticsHelper) === null || _this$analyticsHelper25 === void 0 || _this$analyticsHelper25.sendActionEvent(_const.EVENT_ACTION.COMMIT_UNCONFIRMED_STEPS, _const.EVENT_STATUS.SUCCESS, {
679
- latency: measure === null || measure === void 0 ? void 0 : measure.duration,
680
- // upon success, emit the total number of unconfirmed steps we synced
681
- numUnconfirmedSteps: unconfirmedSteps === null || unconfirmedSteps === void 0 ? void 0 : unconfirmedSteps.length
682
- });
683
- case 24:
684
- _context5.next = 32;
685
- break;
686
- case 26:
687
- _context5.prev = 26;
688
- _context5.t0 = _context5["catch"](1);
689
- _measure3 = (0, _performance.stopMeasure)(_performance.MEASURE_NAME.COMMIT_UNCONFIRMED_STEPS, _this.analyticsHelper);
690
- (_this$analyticsHelper26 = _this.analyticsHelper) === null || _this$analyticsHelper26 === void 0 || _this$analyticsHelper26.sendActionEvent(_const.EVENT_ACTION.COMMIT_UNCONFIRMED_STEPS, _const.EVENT_STATUS.FAILURE, {
691
- latency: _measure3 === null || _measure3 === void 0 ? void 0 : _measure3.duration,
692
- numUnconfirmedSteps: unconfirmedSteps === null || unconfirmedSteps === void 0 ? void 0 : unconfirmedSteps.length
693
- });
694
- (_this$analyticsHelper27 = _this.analyticsHelper) === null || _this$analyticsHelper27 === void 0 || _this$analyticsHelper27.sendErrorEvent(_context5.t0, 'Error while committing unconfirmed steps');
695
- throw _context5.t0;
696
- case 32:
697
- case "end":
698
- return _context5.stop();
699
- }
700
- }, _callee5, null, [[1, 26]]);
701
- })));
702
+ (_this$analyticsHelper27 = _this.analyticsHelper) === null || _this$analyticsHelper27 === void 0 || _this$analyticsHelper27.sendErrorEvent(_context5.t0, 'Error while committing unconfirmed steps');
703
+ throw _context5.t0;
704
+ case 32:
705
+ case "end":
706
+ return _context5.stop();
707
+ }
708
+ }, _callee5, null, [[1, 26]]);
709
+ }));
710
+ return function (_x5) {
711
+ return _ref10.apply(this, arguments);
712
+ };
713
+ }());
702
714
  (0, _defineProperty2.default)(this, "onStepRejectedError", function () {
703
715
  var _this$analyticsHelper28;
704
716
  _this.stepRejectCounter++;
@@ -772,10 +784,21 @@ var DocumentService = exports.DocumentService = /*#__PURE__*/function () {
772
784
  var state = (_this$getState7 = this.getState) === null || _this$getState7 === void 0 ? void 0 : _this$getState7.call(this);
773
785
  var unconfirmedSteps = state ? (_getCollabState = (0, _prosemirrorCollab.getCollabState)(state)) === null || _getCollabState === void 0 ? void 0 : _getCollabState.unconfirmed : undefined;
774
786
  if (steps.length > 0 && state && unconfirmedSteps && unconfirmedSteps.length > 0) {
775
- // In the future we can determine the type of conflict
776
- this.providerEmitCallback('data:conflict', {
777
- offlineDoc: state.doc
787
+ var schema = state.schema,
788
+ tr = state.tr;
789
+ var remoteSteps = steps.map(function (s) {
790
+ return _transform.Step.fromJSON(schema, s);
778
791
  });
792
+ var conflicts = (0, _getConflictChanges.getConflictChanges)({
793
+ localSteps: unconfirmedSteps,
794
+ remoteSteps: remoteSteps,
795
+ tr: tr
796
+ });
797
+ if (conflicts.deleted.length > 0 || conflicts.inserted.length > 0) {
798
+ this.providerEmitCallback('data:conflict', _objectSpread({
799
+ offlineDoc: state.doc
800
+ }, conflicts));
801
+ }
779
802
  }
780
803
  }
781
804
  }, {
@@ -870,7 +893,7 @@ var DocumentService = exports.DocumentService = /*#__PURE__*/function () {
870
893
  */
871
894
  }, {
872
895
  key: "sendStepsFromCurrentState",
873
- value: function sendStepsFromCurrentState(sendAnalyticsEvent, forcePublish) {
896
+ value: function sendStepsFromCurrentState(sendAnalyticsEvent, reason) {
874
897
  var _this$getState8;
875
898
  var state = (_this$getState8 = this.getState) === null || _this$getState8 === void 0 ? void 0 : _this$getState8.call(this);
876
899
  if (!state) {
@@ -878,7 +901,7 @@ var DocumentService = exports.DocumentService = /*#__PURE__*/function () {
878
901
  (_this$analyticsHelper33 = this.analyticsHelper) === null || _this$analyticsHelper33 === void 0 || _this$analyticsHelper33.sendErrorEvent(new Error('Editor state is undefined'), 'sendStepsFromCurrentState called without state');
879
902
  return;
880
903
  }
881
- this.send(null, null, state, sendAnalyticsEvent, forcePublish);
904
+ this.send(null, null, state, sendAnalyticsEvent, reason);
882
905
  }
883
906
  }, {
884
907
  key: "send",
@@ -887,7 +910,8 @@ var DocumentService = exports.DocumentService = /*#__PURE__*/function () {
887
910
  * Send steps from transaction to other participants
888
911
  * It needs the superfluous arguments because we keep the interface of the send API the same as the Synchrony plugin
889
912
  */
890
- function send(tr, _oldState, newState, sendAnalyticsEvent, forcePublish) {
913
+ function send(tr, _oldState, newState, sendAnalyticsEvent, reason // only used for publish and draft-sync events - when called through getFinalAcknowledgedState
914
+ ) {
891
915
  var unconfirmedStepsData = (0, _prosemirrorCollab.sendableSteps)(newState);
892
916
  var version = this.getVersionFromCollabState(newState, 'collab-provider: send');
893
917
 
@@ -970,7 +994,7 @@ var DocumentService = exports.DocumentService = /*#__PURE__*/function () {
970
994
  __livePage: this.options.__livePage,
971
995
  hasRecovered: this.hasRecovered,
972
996
  collabMode: this.participantsService.getCollabMode(),
973
- forcePublish: forcePublish
997
+ reason: reason
974
998
  });
975
999
  }
976
1000
  }]);
@@ -0,0 +1,177 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.getConflictChanges = getConflictChanges;
8
+ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
9
+ var _prosemirrorChangeset = require("prosemirror-changeset");
10
+ 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; }
11
+ 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; }
12
+ var simplifySteps = function simplifySteps(steps) {
13
+ return steps.reduce(function (acc, step) {
14
+ var lastStep = acc[acc.length - 1];
15
+ if (lastStep) {
16
+ var mergedStep = lastStep.merge(step);
17
+ if (mergedStep) {
18
+ acc[acc.length - 1] = mergedStep;
19
+ return acc;
20
+ }
21
+ }
22
+ return acc.concat(step);
23
+ }, []);
24
+ };
25
+ function findContentChanges(doc, steps) {
26
+ var changes = _prosemirrorChangeset.ChangeSet.create(doc);
27
+ var latestDoc = doc;
28
+ simplifySteps(steps).forEach(function (step, index) {
29
+ var stepResult = step.apply(latestDoc);
30
+ if (stepResult.failed !== null || stepResult.doc === null) {
31
+ return;
32
+ }
33
+ latestDoc = stepResult.doc;
34
+ changes = changes.addSteps(latestDoc, [step.getMap()], {
35
+ step: index
36
+ });
37
+ });
38
+ return changes;
39
+ }
40
+
41
+ /**
42
+ * Iterate through the changesets to find overlapping regions that indicate conflicting
43
+ * changes
44
+ */
45
+ var getConflicts = function getConflicts(_ref) {
46
+ var localChanges = _ref.localChanges,
47
+ localDoc = _ref.localDoc,
48
+ remoteChanges = _ref.remoteChanges,
49
+ remoteDoc = _ref.remoteDoc;
50
+ var conflictingChanges = [];
51
+ localChanges.changes.forEach(function (localChange) {
52
+ remoteChanges.changes.forEach(function (remoteChange) {
53
+ if (
54
+ // Local change is inside remote change
55
+ localChange.fromA >= remoteChange.fromA && localChange.toA <= remoteChange.toA ||
56
+ // Remote change is inside local change
57
+ remoteChange.fromA >= localChange.fromA && remoteChange.toA <= localChange.toA ||
58
+ // Partial overlap with the end
59
+ localChange.fromA >= remoteChange.fromA && localChange.fromA < remoteChange.toA && localChange.toA > remoteChange.toA ||
60
+ // Partial overlap with the start
61
+ localChange.fromA < remoteChange.fromA && localChange.toA > remoteChange.fromA && localChange.toA <= remoteChange.toA) {
62
+ conflictingChanges.push({
63
+ from: Math.min(localChange.fromA, remoteChange.fromA),
64
+ to: Math.max(localChange.toA, remoteChange.toA),
65
+ local: localDoc.slice(localChange.fromB, localChange.toB, true),
66
+ remote: remoteDoc.slice(remoteChange.fromB, remoteChange.toB)
67
+ });
68
+ }
69
+ });
70
+ });
71
+ return conflictingChanges;
72
+ };
73
+
74
+ /**
75
+ * Almost a copy of the rebaseSteps in the collab algorithm (which gets called
76
+ * synchronously after this).
77
+ *
78
+ * This also tracks the intermediate documents so we can generate the changesets
79
+ * to use for finding any overlapping regions.
80
+ * See: `packages/editor/prosemirror-collab/src/index.ts`
81
+ */
82
+ var rebaseSteps = function rebaseSteps(_ref2) {
83
+ var _tr$mapping$maps;
84
+ var localSteps = _ref2.localSteps,
85
+ remoteSteps = _ref2.remoteSteps,
86
+ tr = _ref2.tr;
87
+ for (var i = (localSteps === null || localSteps === void 0 ? void 0 : localSteps.length) - 1; i >= 0; i--) {
88
+ tr.step(localSteps[i].inverted);
89
+ }
90
+ var originalDoc = tr.doc;
91
+ var mapStart = (_tr$mapping$maps = tr.mapping.maps) === null || _tr$mapping$maps === void 0 ? void 0 : _tr$mapping$maps.length;
92
+ for (var _i = 0; _i < remoteSteps.length; _i++) {
93
+ tr.step(remoteSteps[_i]);
94
+ }
95
+ var remoteDoc = tr.doc;
96
+ for (var _i2 = 0, mapFrom = localSteps.length; _i2 < localSteps.length; _i2++) {
97
+ var mapped = localSteps[_i2].step.map(tr.mapping.slice(mapFrom));
98
+ mapFrom--;
99
+ if (mapped && !tr.maybeStep(mapped).failed) {
100
+ // Open ticket for setMirror https://github.com/ProseMirror/prosemirror/issues/869
101
+ // @ts-expect-error
102
+ tr.mapping.setMirror(mapFrom, tr.steps.length - 1);
103
+ }
104
+ }
105
+ return {
106
+ mapStart: mapStart,
107
+ originalDoc: originalDoc,
108
+ remoteDoc: remoteDoc
109
+ };
110
+ };
111
+
112
+ /**
113
+ * Gets the conflicts between the local document and the remote document based on steps.
114
+ * It assumes the steps will be rebased using the `prosemirror-collab` algorithm synchronously after this
115
+ * Therefore the `tr` property is based on the document before rebasing.
116
+ *
117
+ * In the future we could possibly use `prosemirror-recreate-steps` (or similar approach)
118
+ * and tweak this to work for arbitrary diffs between offline and remote documents.
119
+ *
120
+ * @param localSteps Local steps applied between now and the server steps
121
+ * @param remoteSteps Steps retrieved from the server
122
+ * @param tr Transaction of the current document (expected to happen with local steps applied, before remote are applied)
123
+ * @returns All the conflicts (inserted + deleted) which can be applied to the current document
124
+ */
125
+ function getConflictChanges(_ref3) {
126
+ var localSteps = _ref3.localSteps,
127
+ remoteSteps = _ref3.remoteSteps,
128
+ tr = _ref3.tr;
129
+ var localDoc = tr.doc;
130
+ var _rebaseSteps = rebaseSteps({
131
+ localSteps: localSteps,
132
+ remoteSteps: remoteSteps,
133
+ tr: tr
134
+ }),
135
+ originalDoc = _rebaseSteps.originalDoc,
136
+ remoteDoc = _rebaseSteps.remoteDoc,
137
+ mapStart = _rebaseSteps.mapStart;
138
+ var localChanges = findContentChanges(originalDoc, localSteps.map(function (s) {
139
+ return s.step;
140
+ }));
141
+ var remoteChanges = findContentChanges(originalDoc, remoteSteps);
142
+
143
+ // This is the mapping between the original document and our final one which can be used to
144
+ // map conflict positions (which are based on the original doc)
145
+ var mapping = tr.mapping.slice(mapStart);
146
+
147
+ // Find the overlapping conflicts - these are based on the positions of the original document so are
148
+ // common to both local and remote documents.
149
+ // The above mapping allows us to bring these positions to where they are in the current document
150
+ var conflictingChanges = getConflicts({
151
+ localChanges: localChanges,
152
+ localDoc: localDoc,
153
+ remoteDoc: remoteDoc,
154
+ remoteChanges: remoteChanges
155
+ });
156
+ var isConflictChange = function isConflictChange(value) {
157
+ return Boolean(value);
158
+ };
159
+ return {
160
+ inserted: conflictingChanges.filter(function (i) {
161
+ return i.remote.size !== 0;
162
+ }).map(function (i) {
163
+ return _objectSpread(_objectSpread({}, i), {}, {
164
+ from: mapping.map(i.from, -1),
165
+ to: mapping.map(i.to)
166
+ });
167
+ }).filter(isConflictChange),
168
+ deleted: conflictingChanges.filter(function (d) {
169
+ return d.remote.size === 0;
170
+ }).map(function (d) {
171
+ return _objectSpread(_objectSpread({}, d), {}, {
172
+ from: mapping.map(d.from),
173
+ to: mapping.map(d.to)
174
+ });
175
+ }).filter(isConflictChange)
176
+ };
177
+ }
@@ -46,7 +46,7 @@ var NullDocumentService = exports.NullDocumentService = /*#__PURE__*/function ()
46
46
  }
47
47
  }, {
48
48
  key: "getFinalAcknowledgedState",
49
- value: function getFinalAcknowledgedState() {
49
+ value: function getFinalAcknowledgedState(reason) {
50
50
  return Promise.resolve({});
51
51
  }
52
52
  }, {