@atlaskit/collab-provider 9.8.0 → 9.9.1

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.
@@ -1,6 +1,7 @@
1
1
  const defaultNCSFeatureFlags = {
2
2
  testFF: false,
3
- socketMessageMetricsFF: false
3
+ socketMessageMetricsFF: false,
4
+ enableFallbackToReconcile: false
4
5
  };
5
6
 
6
7
  /**
@@ -8,8 +9,9 @@ const defaultNCSFeatureFlags = {
8
9
  */
9
10
  const productKeys = {
10
11
  confluence: {
11
- testFF: 'confluence.fe.collab.provider.testFF',
12
- socketMessageMetricsFF: 'confluence.fe.collab.provider.socketMessageMetricsFF'
12
+ testFF: 'confluence.frontend.collab.provider.testFF',
13
+ socketMessageMetricsFF: 'confluence.frontend.collab.provider.socketMessageMetricsFF',
14
+ enableFallbackToReconcile: 'confluence.frontend.collab.provider.enable-fallback-to-reconcile'
13
15
  }
14
16
  };
15
17
  const filterFeatureFlagNames = flags => {
@@ -241,7 +241,7 @@ export class Provider extends Emitter {
241
241
  this.isPreinitializing = false;
242
242
  this.participantsService = new ParticipantsService(this.analyticsHelper, undefined, this.emitCallback, this.config.getUser, this.channel.broadcast, this.channel.sendPresenceJoined, this.getPresenceData, this.setUserId);
243
243
  this.metadataService = new MetadataService(this.emitCallback, this.channel.sendMetadata);
244
- this.documentService = new DocumentService(this.participantsService, this.analyticsHelper, this.channel.fetchCatchup, this.emitCallback, this.channel.broadcast, () => this.userId, this.onErrorHandled, this.metadataService, this.config.failedStepLimitBeforeCatchupOnPublish, this.config.enableErrorOnFailedDocumentApply);
244
+ this.documentService = new DocumentService(this.participantsService, this.analyticsHelper, this.channel.fetchCatchup, this.channel.fetchReconcile, this.emitCallback, this.channel.broadcast, () => this.userId, this.onErrorHandled, this.metadataService, this.config.failedStepLimitBeforeCatchupOnPublish, this.config.enableErrorOnFailedDocumentApply, this.config.featureFlags);
245
245
  this.onSetupPromise = new Promise(resolve => {
246
246
  this.resolveOnSetupPromise = resolve;
247
247
  });
@@ -1,5 +1,5 @@
1
1
  export const name = "@atlaskit/collab-provider";
2
- export const version = "9.8.0";
2
+ export const version = "9.9.1";
3
3
  export const nextMajorVersion = () => {
4
4
  return [Number(version.split('.')[0]) + 1, 0, 0].join('.');
5
5
  };
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@atlaskit/collab-provider",
3
- "version": "9.8.0",
3
+ "version": "9.9.1",
4
4
  "sideEffects": false
5
5
  }
@@ -325,6 +325,112 @@ export var Channel = /*#__PURE__*/function (_Emitter) {
325
325
  return _ref.apply(this, arguments);
326
326
  };
327
327
  }());
328
+ _defineProperty(_assertThisInitialized(_this), "fetchReconcile", /*#__PURE__*/function () {
329
+ var _ref3 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2(currentStateDoc) {
330
+ var _ref4, _this$token2, reqBody, reconcileResponse, _this$analyticsHelper7;
331
+ return _regeneratorRuntime.wrap(function _callee2$(_context2) {
332
+ while (1) switch (_context2.prev = _context2.next) {
333
+ case 0:
334
+ _context2.prev = 0;
335
+ reqBody = JSON.stringify({
336
+ doc: currentStateDoc,
337
+ productId: 'ccollab',
338
+ reason: 'UNKNOWN' // different reason here?
339
+ });
340
+ _context2.t0 = utils;
341
+ _context2.t1 = _this.config;
342
+ _context2.t2 = "document/".concat(encodeURIComponent(_this.config.documentAri), "/reconcile");
343
+ _context2.t3 = _objectSpread;
344
+ _context2.t4 = _objectSpread;
345
+ _context2.t5 = {};
346
+ if (!_this.config.permissionTokenRefresh) {
347
+ _context2.next = 29;
348
+ break;
349
+ }
350
+ if (!((_this$token2 = _this.token) !== null && _this$token2 !== void 0)) {
351
+ _context2.next = 13;
352
+ break;
353
+ }
354
+ _context2.t8 = _this$token2;
355
+ _context2.next = 16;
356
+ break;
357
+ case 13:
358
+ _context2.next = 15;
359
+ return _this.config.permissionTokenRefresh().then(function (token) {
360
+ if (token) {
361
+ _this.setToken(token);
362
+ }
363
+ return token;
364
+ });
365
+ case 15:
366
+ _context2.t8 = _context2.sent;
367
+ case 16:
368
+ _context2.t9 = _ref4 = _context2.t8;
369
+ _context2.t7 = _context2.t9 !== null;
370
+ if (!_context2.t7) {
371
+ _context2.next = 20;
372
+ break;
373
+ }
374
+ _context2.t7 = _ref4 !== void 0;
375
+ case 20:
376
+ if (!_context2.t7) {
377
+ _context2.next = 24;
378
+ break;
379
+ }
380
+ _context2.t10 = _ref4;
381
+ _context2.next = 25;
382
+ break;
383
+ case 24:
384
+ _context2.t10 = undefined;
385
+ case 25:
386
+ _context2.t11 = _context2.t10;
387
+ _context2.t6 = {
388
+ 'x-token': _context2.t11
389
+ };
390
+ _context2.next = 30;
391
+ break;
392
+ case 29:
393
+ _context2.t6 = {};
394
+ case 30:
395
+ _context2.t12 = _context2.t6;
396
+ _context2.t13 = (0, _context2.t4)(_context2.t5, _context2.t12);
397
+ _context2.t14 = {};
398
+ _context2.t15 = {
399
+ 'x-product': getProduct(_this.config.productInfo),
400
+ 'x-subproduct': getSubProduct(_this.config.productInfo),
401
+ 'Content-Type': 'application/json'
402
+ };
403
+ _context2.t16 = (0, _context2.t3)(_context2.t13, _context2.t14, _context2.t15);
404
+ _context2.t17 = reqBody;
405
+ _context2.t18 = {
406
+ headers: _context2.t16,
407
+ method: 'POST',
408
+ body: _context2.t17
409
+ };
410
+ _context2.t19 = {
411
+ path: _context2.t2,
412
+ requestInit: _context2.t18
413
+ };
414
+ _context2.next = 40;
415
+ return _context2.t0.requestService.call(_context2.t0, _context2.t1, _context2.t19);
416
+ case 40:
417
+ reconcileResponse = _context2.sent;
418
+ return _context2.abrupt("return", reconcileResponse);
419
+ case 44:
420
+ _context2.prev = 44;
421
+ _context2.t20 = _context2["catch"](0);
422
+ (_this$analyticsHelper7 = _this.analyticsHelper) === null || _this$analyticsHelper7 === void 0 ? void 0 : _this$analyticsHelper7.sendErrorEvent(_context2.t20, 'Error while fetching reconciled document');
423
+ throw _context2.t20;
424
+ case 48:
425
+ case "end":
426
+ return _context2.stop();
427
+ }
428
+ }, _callee2, null, [[0, 44]]);
429
+ }));
430
+ return function (_x2) {
431
+ return _ref3.apply(this, arguments);
432
+ };
433
+ }());
328
434
  /**
329
435
  * Send message to the back-end service over the channel. Timestamp will be added server side.
330
436
  * @throws {NotInitializedError} Channel not initialized
@@ -409,10 +515,10 @@ export var Channel = /*#__PURE__*/function (_Emitter) {
409
515
  var auth;
410
516
  if (permissionTokenRefresh) {
411
517
  auth = /*#__PURE__*/function () {
412
- var _ref3 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2(cb) {
518
+ var _ref5 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee3(cb) {
413
519
  var authData, token, _data, _data$meta, authenticationError;
414
- return _regeneratorRuntime.wrap(function _callee2$(_context2) {
415
- while (1) switch (_context2.prev = _context2.next) {
520
+ return _regeneratorRuntime.wrap(function _callee3$(_context3) {
521
+ while (1) switch (_context3.prev = _context3.next) {
416
522
  case 0:
417
523
  // Rebuild authData to ensure values are current
418
524
  authData = {
@@ -422,19 +528,19 @@ export var Channel = /*#__PURE__*/function (_Emitter) {
422
528
  need404: _this2.config.need404
423
529
  }; // use the cached token if caching in enabled and token valid
424
530
  if (!(cacheToken && _this2.token)) {
425
- _context2.next = 6;
531
+ _context3.next = 6;
426
532
  break;
427
533
  }
428
534
  authData.token = _this2.token;
429
535
  cb(authData);
430
- _context2.next = 18;
536
+ _context3.next = 18;
431
537
  break;
432
538
  case 6:
433
- _context2.prev = 6;
434
- _context2.next = 9;
539
+ _context3.prev = 6;
540
+ _context3.next = 9;
435
541
  return permissionTokenRefresh();
436
542
  case 9:
437
- token = _context2.sent;
543
+ token = _context3.sent;
438
544
  if (token) {
439
545
  // save token locally
440
546
  _this2.setToken(token);
@@ -444,11 +550,11 @@ export var Channel = /*#__PURE__*/function (_Emitter) {
444
550
  authData.token = undefined;
445
551
  }
446
552
  cb(authData);
447
- _context2.next = 18;
553
+ _context3.next = 18;
448
554
  break;
449
555
  case 14:
450
- _context2.prev = 14;
451
- _context2.t0 = _context2["catch"](6);
556
+ _context3.prev = 14;
557
+ _context3.t0 = _context3["catch"](6);
452
558
  // Pass the error back to the consumers so they can deal with exceptional cases themselves (eg. no permissions because the page was deleted)
453
559
  authenticationError = {
454
560
  message: 'Insufficient editing permissions',
@@ -456,8 +562,8 @@ export var Channel = /*#__PURE__*/function (_Emitter) {
456
562
  status: 403,
457
563
  code: INTERNAL_ERROR_CODE.TOKEN_PERMISSION_ERROR,
458
564
  meta: {
459
- originalError: _context2.t0,
460
- reason: _context2.t0 === null || _context2.t0 === void 0 ? void 0 : (_data = _context2.t0.data) === null || _data === void 0 ? void 0 : (_data$meta = _data.meta) === null || _data$meta === void 0 ? void 0 : _data$meta.reason // Should always be 'RESOURCE_DELETED' Temporary, until Confluence Cloud removes their hack
565
+ originalError: _context3.t0,
566
+ reason: _context3.t0 === null || _context3.t0 === void 0 ? void 0 : (_data = _context3.t0.data) === null || _data === void 0 ? void 0 : (_data$meta = _data.meta) === null || _data$meta === void 0 ? void 0 : _data$meta.reason // Should always be 'RESOURCE_DELETED' Temporary, until Confluence Cloud removes their hack
461
567
  // https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/browse/next/packages/native-collab/src/fetchCollabPermissionToken.ts#37
462
568
  }
463
569
  }
@@ -466,12 +572,12 @@ export var Channel = /*#__PURE__*/function (_Emitter) {
466
572
  _this2.emit('error', authenticationError);
467
573
  case 18:
468
574
  case "end":
469
- return _context2.stop();
575
+ return _context3.stop();
470
576
  }
471
- }, _callee2, null, [[6, 14]]);
577
+ }, _callee3, null, [[6, 14]]);
472
578
  }));
473
- return function auth(_x2) {
474
- return _ref3.apply(this, arguments);
579
+ return function auth(_x3) {
580
+ return _ref5.apply(this, arguments);
475
581
  };
476
582
  }();
477
583
  } else {
@@ -495,9 +601,9 @@ export var Channel = /*#__PURE__*/function (_Emitter) {
495
601
  this.socket.on('steps:added', function (data) {
496
602
  _this2.emit('steps:added', data);
497
603
  });
498
- this.socket.on('participant:telepointer', function (_ref4) {
499
- var timestamp = _ref4.timestamp,
500
- data = _ref4.data;
604
+ this.socket.on('participant:telepointer', function (_ref6) {
605
+ var timestamp = _ref6.timestamp,
606
+ data = _ref6.data;
501
607
  // data is TelepointerPayload without timestamp
502
608
  _this2.emit('participant:telepointer', _objectSpread({
503
609
  timestamp: timestamp
@@ -512,11 +618,11 @@ export var Channel = /*#__PURE__*/function (_Emitter) {
512
618
  this.socket.on('participant:left', function (data) {
513
619
  _this2.emit('participant:left', data);
514
620
  });
515
- this.socket.on('participant:updated', function (_ref5) {
516
- var sessionId = _ref5.sessionId,
517
- timestamp = _ref5.timestamp,
518
- data = _ref5.data,
519
- clientId = _ref5.clientId;
621
+ this.socket.on('participant:updated', function (_ref7) {
622
+ var sessionId = _ref7.sessionId,
623
+ timestamp = _ref7.timestamp,
624
+ data = _ref7.data,
625
+ clientId = _ref7.clientId;
520
626
  _this2.emit('participant:updated', _objectSpread({
521
627
  sessionId: sessionId,
522
628
  timestamp: timestamp,
@@ -532,10 +638,10 @@ export var Channel = /*#__PURE__*/function (_Emitter) {
532
638
  _this2.emit('status', data);
533
639
  });
534
640
  this.socket.on('disconnect', /*#__PURE__*/function () {
535
- var _ref6 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee3(reason) {
641
+ var _ref8 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee4(reason) {
536
642
  var _this2$analyticsHelpe, reconnectionError;
537
- return _regeneratorRuntime.wrap(function _callee3$(_context3) {
538
- while (1) switch (_context3.prev = _context3.next) {
643
+ return _regeneratorRuntime.wrap(function _callee4$(_context4) {
644
+ while (1) switch (_context4.prev = _context4.next) {
539
645
  case 0:
540
646
  if (getCollabProviderFeatureFlag('socketMessageMetricsFF', _this2.config.featureFlags) && _this2.socketMessageMetrics) {
541
647
  _this2.socketMessageMetrics.closeSocketMessageMetrics();
@@ -563,12 +669,12 @@ export var Channel = /*#__PURE__*/function (_Emitter) {
563
669
  }
564
670
  case 5:
565
671
  case "end":
566
- return _context3.stop();
672
+ return _context4.stop();
567
673
  }
568
- }, _callee3);
674
+ }, _callee4);
569
675
  }));
570
- return function (_x3) {
571
- return _ref6.apply(this, arguments);
676
+ return function (_x4) {
677
+ return _ref8.apply(this, arguments);
572
678
  };
573
679
  }());
574
680
 
@@ -658,8 +764,8 @@ export var Channel = /*#__PURE__*/function (_Emitter) {
658
764
  this.emit('error', rateLimitError);
659
765
  throw new Error();
660
766
  } 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');
767
+ var _this$analyticsHelper8;
768
+ (_this$analyticsHelper8 = this.analyticsHelper) === null || _this$analyticsHelper8 === void 0 ? void 0 : _this$analyticsHelper8.sendErrorEvent(rateLimitError, 'Rate limited');
663
769
  }
664
770
  }
665
771
  }
@@ -15,6 +15,7 @@ import { JSONTransformer } from '@atlaskit/editor-json-transformer';
15
15
  import { MAX_STEP_REJECTED_ERROR } from '../provider';
16
16
  import { catchup } from './catchup';
17
17
  import { StepQueueState } from './step-queue-state';
18
+ import { getCollabProviderFeatureFlag } from '../feature-flags';
18
19
  import { CantSyncUpError, INTERNAL_ERROR_CODE, UpdateDocumentError } from '../errors/error-types';
19
20
  var CATCHUP_THROTTLE = 1 * 1000; // 1 second
20
21
 
@@ -29,6 +30,7 @@ export var DocumentService = /*#__PURE__*/function () {
29
30
  * and to emit their telepointers from steps they add
30
31
  * @param analyticsHelper - Helper for analytics events
31
32
  * @param fetchCatchup - Function to fetch "catchup" data, data required to rebase current steps to the latest version.
33
+ * @param fetchReconcile - Function to call "reconcile" from NCS backend
32
34
  * @param providerEmitCallback - Callback for emitting events to listeners on the provider
33
35
  * @param broadcast - Callback for broadcasting events to other clients
34
36
  * @param getUserId - Callback to fetch the current user's ID
@@ -36,11 +38,13 @@ export var DocumentService = /*#__PURE__*/function () {
36
38
  * @param metadataService
37
39
  * @param failedStepsBeforeCatchupOnPublish - Control MAX_STEP_REJECTED_ERROR during page publishes.
38
40
  * @param enableErrorOnFailedDocumentApply - Enable failed document update exceptions.
41
+ * @param featureFlags - Feature flag config
39
42
  */
40
- function DocumentService(participantsService, analyticsHelper, fetchCatchup, providerEmitCallback, broadcast, getUserId, onErrorHandled, metadataService) {
43
+ function DocumentService(participantsService, analyticsHelper, fetchCatchup, fetchReconcile, providerEmitCallback, broadcast, getUserId, onErrorHandled, metadataService) {
41
44
  var _this = this;
42
- var failedStepsBeforeCatchupOnPublish = arguments.length > 8 && arguments[8] !== undefined ? arguments[8] : MAX_STEP_REJECTED_ERROR;
43
- var enableErrorOnFailedDocumentApply = arguments.length > 9 && arguments[9] !== undefined ? arguments[9] : false;
45
+ var failedStepsBeforeCatchupOnPublish = arguments.length > 9 && arguments[9] !== undefined ? arguments[9] : MAX_STEP_REJECTED_ERROR;
46
+ var enableErrorOnFailedDocumentApply = arguments.length > 10 && arguments[10] !== undefined ? arguments[10] : false;
47
+ var featureFlags = arguments.length > 11 ? arguments[11] : undefined;
44
48
  _classCallCheck(this, DocumentService);
45
49
  // Fires analytics to editor when collab editor cannot sync up
46
50
  _defineProperty(this, "stepRejectCounter", 0);
@@ -276,41 +280,76 @@ export var DocumentService = /*#__PURE__*/function () {
276
280
  }
277
281
  });
278
282
  _defineProperty(this, "getFinalAcknowledgedState", /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee3() {
279
- var _this$analyticsHelper14, currentState, measure, _this$analyticsHelper15, _this$analyticsHelper16, _measure2;
283
+ var _this$analyticsHelper14, finalAcknowledgedState, currentState, reconcileResponse, measure, _this$analyticsHelper15, _this$analyticsHelper16, _measure2;
280
284
  return _regeneratorRuntime.wrap(function _callee3$(_context3) {
281
285
  while (1) switch (_context3.prev = _context3.next) {
282
286
  case 0:
283
287
  _this.aggressiveCatchup = true;
284
288
  _context3.prev = 1;
285
289
  startMeasure(MEASURE_NAME.PUBLISH_PAGE, _this.analyticsHelper);
286
- _context3.next = 5;
287
- return _this.commitUnconfirmedSteps();
288
- case 5:
290
+ if (!getCollabProviderFeatureFlag('enableFallbackToReconcile', _this.featureFlags)) {
291
+ _context3.next = 23;
292
+ break;
293
+ }
294
+ _context3.prev = 4;
289
295
  _context3.next = 7;
290
- return _this.getCurrentState();
296
+ return _this.commitUnconfirmedSteps();
291
297
  case 7:
298
+ _context3.next = 9;
299
+ return _this.getCurrentState();
300
+ case 9:
301
+ finalAcknowledgedState = _context3.sent;
302
+ _context3.next = 21;
303
+ break;
304
+ case 12:
305
+ _context3.prev = 12;
306
+ _context3.t0 = _context3["catch"](4);
307
+ _context3.next = 16;
308
+ return _this.getCurrentState();
309
+ case 16:
292
310
  currentState = _context3.sent;
311
+ _context3.next = 19;
312
+ return _this.fetchReconcile(JSON.stringify(currentState.content));
313
+ case 19:
314
+ reconcileResponse = _context3.sent;
315
+ finalAcknowledgedState = {
316
+ content: JSON.parse(reconcileResponse.document),
317
+ title: currentState.title,
318
+ stepVersion: reconcileResponse.version
319
+ };
320
+ case 21:
321
+ _context3.next = 28;
322
+ break;
323
+ case 23:
324
+ _context3.next = 25;
325
+ return _this.commitUnconfirmedSteps();
326
+ case 25:
327
+ _context3.next = 27;
328
+ return _this.getCurrentState();
329
+ case 27:
330
+ finalAcknowledgedState = _context3.sent;
331
+ case 28:
293
332
  measure = stopMeasure(MEASURE_NAME.PUBLISH_PAGE, _this.analyticsHelper);
294
333
  (_this$analyticsHelper14 = _this.analyticsHelper) === null || _this$analyticsHelper14 === void 0 ? void 0 : _this$analyticsHelper14.sendActionEvent(EVENT_ACTION.PUBLISH_PAGE, EVENT_STATUS.SUCCESS, {
295
334
  latency: measure === null || measure === void 0 ? void 0 : measure.duration
296
335
  });
297
336
  _this.aggressiveCatchup = false;
298
- return _context3.abrupt("return", currentState);
299
- case 14:
300
- _context3.prev = 14;
301
- _context3.t0 = _context3["catch"](1);
337
+ return _context3.abrupt("return", finalAcknowledgedState);
338
+ case 34:
339
+ _context3.prev = 34;
340
+ _context3.t1 = _context3["catch"](1);
302
341
  _this.aggressiveCatchup = false;
303
342
  _measure2 = stopMeasure(MEASURE_NAME.PUBLISH_PAGE, _this.analyticsHelper);
304
343
  (_this$analyticsHelper15 = _this.analyticsHelper) === null || _this$analyticsHelper15 === void 0 ? void 0 : _this$analyticsHelper15.sendActionEvent(EVENT_ACTION.PUBLISH_PAGE, EVENT_STATUS.FAILURE, {
305
344
  latency: _measure2 === null || _measure2 === void 0 ? void 0 : _measure2.duration
306
345
  });
307
- (_this$analyticsHelper16 = _this.analyticsHelper) === null || _this$analyticsHelper16 === void 0 ? void 0 : _this$analyticsHelper16.sendErrorEvent(_context3.t0, 'Error while returning ADF version of the final draft document');
308
- throw _context3.t0;
309
- case 21:
346
+ (_this$analyticsHelper16 = _this.analyticsHelper) === null || _this$analyticsHelper16 === void 0 ? void 0 : _this$analyticsHelper16.sendErrorEvent(_context3.t1, 'Error while returning ADF version of the final draft document');
347
+ throw _context3.t1;
348
+ case 41:
310
349
  case "end":
311
350
  return _context3.stop();
312
351
  }
313
- }, _callee3, null, [[1, 14]]);
352
+ }, _callee3, null, [[1, 34], [4, 12]]);
314
353
  })));
315
354
  _defineProperty(this, "updateDocument", function (_ref6) {
316
355
  var doc = _ref6.doc,
@@ -488,6 +527,7 @@ export var DocumentService = /*#__PURE__*/function () {
488
527
  this.participantsService = participantsService;
489
528
  this.analyticsHelper = analyticsHelper;
490
529
  this.fetchCatchup = fetchCatchup;
530
+ this.fetchReconcile = fetchReconcile;
491
531
  this.providerEmitCallback = providerEmitCallback;
492
532
  this.broadcast = broadcast;
493
533
  this.getUserId = getUserId;
@@ -495,6 +535,7 @@ export var DocumentService = /*#__PURE__*/function () {
495
535
  this.metadataService = metadataService;
496
536
  this.failedStepsBeforeCatchupOnPublish = failedStepsBeforeCatchupOnPublish;
497
537
  this.enableErrorOnFailedDocumentApply = enableErrorOnFailedDocumentApply;
538
+ this.featureFlags = featureFlags;
498
539
  this.stepQueue = new StepQueueState();
499
540
  }
500
541
  _createClass(DocumentService, [{
@@ -3,9 +3,10 @@ describe('Feature flags', function () {
3
3
  it('getProductSpecificFeatureFlags', function () {
4
4
  var result = getProductSpecificFeatureFlags({
5
5
  testFF: true,
6
- socketMessageMetricsFF: true
6
+ socketMessageMetricsFF: true,
7
+ enableFallbackToReconcile: true
7
8
  }, 'confluence');
8
- expect(result).toEqual(['confluence.fe.collab.provider.testFF', 'confluence.fe.collab.provider.socketMessageMetricsFF']);
9
+ expect(result).toEqual(['confluence.frontend.collab.provider.testFF', 'confluence.frontend.collab.provider.socketMessageMetricsFF', 'confluence.frontend.collab.provider.enable-fallback-to-reconcile']);
9
10
  });
10
11
  it('getCollabProviderFeatureFlag return true', function () {
11
12
  var result = getCollabProviderFeatureFlag('testFF', {
@@ -1,7 +1,8 @@
1
1
  import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
2
2
  var defaultNCSFeatureFlags = {
3
3
  testFF: false,
4
- socketMessageMetricsFF: false
4
+ socketMessageMetricsFF: false,
5
+ enableFallbackToReconcile: false
5
6
  };
6
7
 
7
8
  /**
@@ -9,8 +10,9 @@ var defaultNCSFeatureFlags = {
9
10
  */
10
11
  var productKeys = {
11
12
  confluence: {
12
- testFF: 'confluence.fe.collab.provider.testFF',
13
- socketMessageMetricsFF: 'confluence.fe.collab.provider.socketMessageMetricsFF'
13
+ testFF: 'confluence.frontend.collab.provider.testFF',
14
+ socketMessageMetricsFF: 'confluence.frontend.collab.provider.socketMessageMetricsFF',
15
+ enableFallbackToReconcile: 'confluence.frontend.collab.provider.enable-fallback-to-reconcile'
14
16
  }
15
17
  };
16
18
  var filterFeatureFlagNames = function filterFeatureFlagNames(flags) {
@@ -279,9 +279,9 @@ export var Provider = /*#__PURE__*/function (_Emitter) {
279
279
  _this.isPreinitializing = false;
280
280
  _this.participantsService = new ParticipantsService(_this.analyticsHelper, undefined, _this.emitCallback, _this.config.getUser, _this.channel.broadcast, _this.channel.sendPresenceJoined, _this.getPresenceData, _this.setUserId);
281
281
  _this.metadataService = new MetadataService(_this.emitCallback, _this.channel.sendMetadata);
282
- _this.documentService = new DocumentService(_this.participantsService, _this.analyticsHelper, _this.channel.fetchCatchup, _this.emitCallback, _this.channel.broadcast, function () {
282
+ _this.documentService = new DocumentService(_this.participantsService, _this.analyticsHelper, _this.channel.fetchCatchup, _this.channel.fetchReconcile, _this.emitCallback, _this.channel.broadcast, function () {
283
283
  return _this.userId;
284
- }, _this.onErrorHandled, _this.metadataService, _this.config.failedStepLimitBeforeCatchupOnPublish, _this.config.enableErrorOnFailedDocumentApply);
284
+ }, _this.onErrorHandled, _this.metadataService, _this.config.failedStepLimitBeforeCatchupOnPublish, _this.config.enableErrorOnFailedDocumentApply, _this.config.featureFlags);
285
285
  _this.onSetupPromise = new Promise(function (resolve) {
286
286
  _this.resolveOnSetupPromise = resolve;
287
287
  });
@@ -1,5 +1,5 @@
1
1
  export var name = "@atlaskit/collab-provider";
2
- export var version = "9.8.0";
2
+ export var version = "9.9.1";
3
3
  export var nextMajorVersion = function nextMajorVersion() {
4
4
  return [Number(version.split('.')[0]) + 1, 0, 0].join('.');
5
5
  };
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@atlaskit/collab-provider",
3
- "version": "9.8.0",
3
+ "version": "9.9.1",
4
4
  "sideEffects": false
5
5
  }
@@ -1,5 +1,5 @@
1
1
  import { Emitter } from './emitter';
2
- import type { Config, ChannelEvent, CatchupResponse } from './types';
2
+ import type { Config, ChannelEvent, CatchupResponse, ReconcileResponse } from './types';
3
3
  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';
@@ -41,6 +41,7 @@ export declare class Channel extends Emitter<ChannelEvent> {
41
41
  private onConnect;
42
42
  private onReceiveData;
43
43
  fetchCatchup: (fromVersion: number) => Promise<CatchupResponse>;
44
+ fetchReconcile: (currentStateDoc: string) => Promise<ReconcileResponse>;
44
45
  /**
45
46
  * Send message to the back-end service over the channel. Timestamp will be added server side.
46
47
  * @throws {NotInitializedError} Channel not initialized
@@ -1,6 +1,6 @@
1
1
  /// <reference types="lodash" />
2
2
  import AnalyticsHelper from '../analytics/analytics-helper';
3
- import { CatchupResponse, ChannelEvent, StepsPayload } from '../types';
3
+ import { CatchupResponse, ReconcileResponse, ChannelEvent, StepsPayload } from '../types';
4
4
  import type { SyncUpErrorFunction, ResolvedEditorState } from '@atlaskit/editor-common/collab';
5
5
  import type { Step as ProseMirrorStep } from '@atlaskit/editor-prosemirror/transform';
6
6
  import type { MetadataService } from '../metadata/metadata-service';
@@ -12,6 +12,7 @@ export declare class DocumentService {
12
12
  private participantsService;
13
13
  private analyticsHelper;
14
14
  private fetchCatchup;
15
+ private fetchReconcile;
15
16
  private providerEmitCallback;
16
17
  private broadcast;
17
18
  private getUserId;
@@ -19,6 +20,7 @@ export declare class DocumentService {
19
20
  private metadataService;
20
21
  private failedStepsBeforeCatchupOnPublish;
21
22
  private enableErrorOnFailedDocumentApply;
23
+ private featureFlags;
22
24
  private getState;
23
25
  private onSyncUpError?;
24
26
  private stepQueue;
@@ -31,6 +33,7 @@ export declare class DocumentService {
31
33
  * and to emit their telepointers from steps they add
32
34
  * @param analyticsHelper - Helper for analytics events
33
35
  * @param fetchCatchup - Function to fetch "catchup" data, data required to rebase current steps to the latest version.
36
+ * @param fetchReconcile - Function to call "reconcile" from NCS backend
34
37
  * @param providerEmitCallback - Callback for emitting events to listeners on the provider
35
38
  * @param broadcast - Callback for broadcasting events to other clients
36
39
  * @param getUserId - Callback to fetch the current user's ID
@@ -38,8 +41,11 @@ export declare class DocumentService {
38
41
  * @param metadataService
39
42
  * @param failedStepsBeforeCatchupOnPublish - Control MAX_STEP_REJECTED_ERROR during page publishes.
40
43
  * @param enableErrorOnFailedDocumentApply - Enable failed document update exceptions.
44
+ * @param featureFlags - Feature flag config
41
45
  */
42
- constructor(participantsService: ParticipantsService, analyticsHelper: AnalyticsHelper | undefined, fetchCatchup: (fromVersion: number) => Promise<CatchupResponse>, providerEmitCallback: (evt: keyof CollabEvents, data: any) => void, broadcast: <K extends keyof ChannelEvent>(type: K, data: Omit<ChannelEvent[K], 'timestamp'>, callback?: Function) => void, getUserId: () => string | undefined, onErrorHandled: (error: InternalError) => void, metadataService: MetadataService, failedStepsBeforeCatchupOnPublish?: number, enableErrorOnFailedDocumentApply?: boolean);
46
+ constructor(participantsService: ParticipantsService, analyticsHelper: AnalyticsHelper | undefined, fetchCatchup: (fromVersion: number) => Promise<CatchupResponse>, fetchReconcile: (currentStateDoc: string) => Promise<ReconcileResponse>, providerEmitCallback: (evt: keyof CollabEvents, data: any) => void, broadcast: <K extends keyof ChannelEvent>(type: K, data: Omit<ChannelEvent[K], 'timestamp'>, callback?: Function) => void, getUserId: () => string | undefined, onErrorHandled: (error: InternalError) => void, metadataService: MetadataService, failedStepsBeforeCatchupOnPublish: number, enableErrorOnFailedDocumentApply: boolean, featureFlags: {
47
+ [key: string]: boolean;
48
+ } | undefined);
43
49
  /**
44
50
  * To prevent calling catchup to often, use lodash throttle to reduce the frequency
45
51
  */
@@ -1,6 +1,7 @@
1
1
  export interface NCSFeatureFlags {
2
2
  testFF?: boolean;
3
3
  socketMessageMetricsFF?: boolean;
4
+ enableFallbackToReconcile?: boolean;
4
5
  }
5
6
  export interface WithNCSFeatureFlags {
6
7
  featureFlags?: NCSFeatureFlags;
@@ -179,6 +179,12 @@ export interface CatchupResponse {
179
179
  stepMaps?: any[];
180
180
  metadata?: Metadata;
181
181
  }
182
+ export interface ReconcileResponse {
183
+ document: string;
184
+ version: number;
185
+ ari?: string;
186
+ metadata?: Metadata;
187
+ }
182
188
  export interface CatchupOptions {
183
189
  getCurrentPmVersion: () => number;
184
190
  fetchCatchup: (fromVersion: number) => Promise<CatchupResponse>;
@@ -1,5 +1,5 @@
1
1
  import { Emitter } from './emitter';
2
- import type { Config, ChannelEvent, CatchupResponse } from './types';
2
+ import type { Config, ChannelEvent, CatchupResponse, ReconcileResponse } from './types';
3
3
  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';
@@ -41,6 +41,7 @@ export declare class Channel extends Emitter<ChannelEvent> {
41
41
  private onConnect;
42
42
  private onReceiveData;
43
43
  fetchCatchup: (fromVersion: number) => Promise<CatchupResponse>;
44
+ fetchReconcile: (currentStateDoc: string) => Promise<ReconcileResponse>;
44
45
  /**
45
46
  * Send message to the back-end service over the channel. Timestamp will be added server side.
46
47
  * @throws {NotInitializedError} Channel not initialized