@atlaskit/collab-provider 7.7.0 → 8.0.0

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 (36) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/cjs/analytics/index.js +17 -27
  3. package/dist/cjs/analytics/performance.js +0 -2
  4. package/dist/cjs/channel.js +74 -35
  5. package/dist/cjs/helpers/const.js +10 -1
  6. package/dist/cjs/provider/catchup.js +2 -2
  7. package/dist/cjs/provider/index.js +174 -65
  8. package/dist/cjs/types.js +10 -1
  9. package/dist/cjs/version-wrapper.js +1 -1
  10. package/dist/cjs/version.json +1 -1
  11. package/dist/es2019/analytics/index.js +15 -22
  12. package/dist/es2019/analytics/performance.js +0 -2
  13. package/dist/es2019/channel.js +28 -10
  14. package/dist/es2019/helpers/const.js +8 -0
  15. package/dist/es2019/provider/catchup.js +2 -2
  16. package/dist/es2019/provider/index.js +165 -65
  17. package/dist/es2019/types.js +7 -1
  18. package/dist/es2019/version-wrapper.js +1 -1
  19. package/dist/es2019/version.json +1 -1
  20. package/dist/esm/analytics/index.js +15 -22
  21. package/dist/esm/analytics/performance.js +0 -2
  22. package/dist/esm/channel.js +75 -36
  23. package/dist/esm/helpers/const.js +8 -0
  24. package/dist/esm/provider/catchup.js +2 -2
  25. package/dist/esm/provider/index.js +175 -69
  26. package/dist/esm/types.js +7 -1
  27. package/dist/esm/version-wrapper.js +1 -1
  28. package/dist/esm/version.json +1 -1
  29. package/dist/types/analytics/index.d.ts +1 -3
  30. package/dist/types/analytics/performance.d.ts +0 -2
  31. package/dist/types/channel.d.ts +2 -1
  32. package/dist/types/helpers/const.d.ts +30 -2
  33. package/dist/types/provider/index.d.ts +10 -6
  34. package/dist/types/types.d.ts +32 -12
  35. package/package.json +9 -6
  36. package/report.api.md +13 -9
@@ -5,8 +5,9 @@ import { Emitter } from './emitter';
5
5
  import { ErrorCodeMapper } from './error-code-mapper';
6
6
  import { createLogger, getProduct, getSubProduct } from './helpers/utils';
7
7
  import { MEASURE_NAME, startMeasure, stopMeasure } from './analytics/performance';
8
- import { triggerCollabAnalyticsEvent } from './analytics';
8
+ import { triggerAnalyticsEvent } from './analytics';
9
9
  import { EVENT_ACTION, EVENT_STATUS } from './helpers/const';
10
+ import { ExperiencePerformanceTypes, ExperienceTypes, UFOExperience } from '@atlaskit/ufo';
10
11
  const logger = createLogger('Channel', 'green');
11
12
  export class Channel extends Emitter {
12
13
  constructor(config) {
@@ -18,6 +19,18 @@ export class Channel extends Emitter {
18
19
 
19
20
  _defineProperty(this, "initialized", false);
20
21
 
22
+ _defineProperty(this, "initExperience", new UFOExperience('collab-provider.document-init', {
23
+ type: ExperienceTypes.Load,
24
+ performanceType: ExperiencePerformanceTypes.Custom,
25
+ performanceConfig: {
26
+ histogram: {
27
+ [ExperiencePerformanceTypes.Custom]: {
28
+ duration: '250_500_1000_1500_2000_3000_4000'
29
+ }
30
+ }
31
+ }
32
+ }));
33
+
21
34
  _defineProperty(this, "getInitialized", () => this.initialized);
22
35
 
23
36
  _defineProperty(this, "getConnected", () => this.connected);
@@ -28,7 +41,7 @@ export class Channel extends Emitter {
28
41
  this.connected = true;
29
42
  logger('Connected.', this.socket.id);
30
43
  const measure = stopMeasure(MEASURE_NAME.SOCKET_CONNECT);
31
- triggerCollabAnalyticsEvent({
44
+ triggerAnalyticsEvent({
32
45
  eventAction: EVENT_ACTION.CONNECTION,
33
46
  attributes: {
34
47
  eventStatus: EVENT_STATUS.SUCCESS,
@@ -49,7 +62,8 @@ export class Channel extends Emitter {
49
62
  if (data.type === 'initial') {
50
63
  if (!this.initialized) {
51
64
  const measure = stopMeasure(MEASURE_NAME.DOCUMENT_INIT);
52
- triggerCollabAnalyticsEvent({
65
+ this.initExperience.success();
66
+ triggerAnalyticsEvent({
53
67
  eventAction: EVENT_ACTION.DOCUMENT_INIT,
54
68
  attributes: {
55
69
  eventStatus: EVENT_STATUS.SUCCESS,
@@ -107,6 +121,7 @@ export class Channel extends Emitter {
107
121
 
108
122
  if (!this.initialized) {
109
123
  startMeasure(MEASURE_NAME.DOCUMENT_INIT);
124
+ this.initExperience.start();
110
125
  }
111
126
 
112
127
  const {
@@ -124,9 +139,10 @@ export class Channel extends Emitter {
124
139
  if (permissionTokenRefresh) {
125
140
  authCb = cb => {
126
141
  permissionTokenRefresh().then(token => {
127
- cb({
128
- // The permission token.
129
- token,
142
+ cb({ // The permission token.
143
+ ...(token ? {
144
+ token
145
+ } : {}),
130
146
  // The initialized status. If false, BE will send document, otherwise not.
131
147
  initialized: this.initialized,
132
148
  // ESS-1009 Allow to opt-in into 404 response
@@ -208,7 +224,7 @@ export class Channel extends Emitter {
208
224
 
209
225
  this.socket.on('connect_error', error => {
210
226
  const measure = stopMeasure(MEASURE_NAME.SOCKET_CONNECT);
211
- triggerCollabAnalyticsEvent({
227
+ triggerAnalyticsEvent({
212
228
  eventAction: EVENT_ACTION.CONNECTION,
213
229
  attributes: {
214
230
  eventStatus: EVENT_STATUS.FAILURE,
@@ -236,6 +252,8 @@ export class Channel extends Emitter {
236
252
 
237
253
  async fetchCatchup(fromVersion) {
238
254
  try {
255
+ var _await$this$config$pe;
256
+
239
257
  const {
240
258
  doc,
241
259
  version,
@@ -248,7 +266,7 @@ export class Channel extends Emitter {
248
266
  },
249
267
  requestInit: {
250
268
  headers: { ...(this.config.permissionTokenRefresh ? {
251
- 'x-token': await this.config.permissionTokenRefresh()
269
+ 'x-token': (_await$this$config$pe = await this.config.permissionTokenRefresh()) !== null && _await$this$config$pe !== void 0 ? _await$this$config$pe : undefined
252
270
  } : {}),
253
271
  'x-product': getProduct(this.config.productInfo),
254
272
  'x-subproduct': getSubProduct(this.config.productInfo)
@@ -279,7 +297,7 @@ export class Channel extends Emitter {
279
297
  */
280
298
 
281
299
 
282
- broadcast(type, data) {
300
+ broadcast(type, data, callback) {
283
301
  if (!this.connected || !this.socket) {
284
302
  return;
285
303
  }
@@ -287,7 +305,7 @@ export class Channel extends Emitter {
287
305
  this.socket.emit('broadcast', {
288
306
  type,
289
307
  ...data
290
- });
308
+ }, callback);
291
309
  }
292
310
 
293
311
  sendMetadata(metadata) {
@@ -26,4 +26,12 @@ export let EVENT_STATUS;
26
26
  EVENT_STATUS["FAILURE"] = "FAILURE";
27
27
  })(EVENT_STATUS || (EVENT_STATUS = {}));
28
28
 
29
+ export let ADD_STEPS_TYPE;
30
+
31
+ (function (ADD_STEPS_TYPE) {
32
+ ADD_STEPS_TYPE["ACCEPTED"] = "ACCEPTED";
33
+ ADD_STEPS_TYPE["REJECTED"] = "REJECTED";
34
+ ADD_STEPS_TYPE["ERROR"] = "ERROR";
35
+ })(ADD_STEPS_TYPE || (ADD_STEPS_TYPE = {}));
36
+
29
37
  export const ACK_MAX_TRY = 30;
@@ -50,7 +50,7 @@ export const catchup = async opt => {
50
50
  * newer.
51
51
  */
52
52
 
53
- opt.fitlerQueue(data => data.version > serverVersion); // We are too far behind - replace the entire document
53
+ opt.filterQueue(data => data.version > serverVersion); // We are too far behind - replace the entire document
54
54
 
55
55
  logger(`Replacing document: ${doc}`);
56
56
  logger(`getting metadata: ${metadata}`); // Replace local document and version number
@@ -84,7 +84,7 @@ export const catchup = async opt => {
84
84
  const newUnconfirmedSteps = rebaseSteps(unconfirmedSteps, mapping);
85
85
  logger(`Re-aply ${newUnconfirmedSteps.length} mapped unconfirmed steps: ${JSON.stringify(newUnconfirmedSteps)}`); // Re-aply local steps
86
86
 
87
- opt.applyLocalsteps(newUnconfirmedSteps);
87
+ opt.applyLocalSteps(newUnconfirmedSteps);
88
88
  }
89
89
  }
90
90
  }
@@ -1,15 +1,18 @@
1
1
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
2
  import { getVersion, sendableSteps } from 'prosemirror-collab';
3
3
  import throttle from 'lodash/throttle';
4
- import isequal from 'lodash/isEqual';
4
+ import isEqual from 'lodash/isEqual';
5
+ import countBy from 'lodash/countBy';
5
6
  import { JSONTransformer } from '@atlaskit/editor-json-transformer';
7
+ import { ExperiencePerformanceTypes, ExperienceTypes, UFOExperience } from '@atlaskit/ufo';
6
8
  import { Emitter } from '../emitter';
7
9
  import { Channel } from '../channel';
8
10
  import { createLogger, getParticipant, sleep } from '../helpers/utils';
9
- import { ACK_MAX_TRY, EVENT_ACTION, EVENT_STATUS } from '../helpers/const';
10
- import { triggerCollabAnalyticsEvent } from '../analytics';
11
+ import { ACK_MAX_TRY, EVENT_ACTION, EVENT_STATUS, ADD_STEPS_TYPE } from '../helpers/const';
12
+ import { triggerAnalyticsEvent } from '../analytics';
11
13
  import { catchup } from './catchup';
12
14
  import { errorCodeMapper } from '../error-code-mapper';
15
+ import { AcknowledgementResponseTypes } from '../types';
13
16
  import { DisconnectReason, socketIOReasons } from '../disconnected-reason-mapper';
14
17
  import { MEASURE_NAME, startMeasure, stopMeasure } from '../analytics/performance';
15
18
  const logger = createLogger('Provider', 'black');
@@ -32,16 +35,71 @@ const commitStep = ({
32
35
  steps,
33
36
  version,
34
37
  userId,
35
- clientId
38
+ clientId,
39
+ documentAri,
40
+ analyticsClient,
41
+ onStepsAdded,
42
+ onErrorHandled
36
43
  }) => {
37
44
  const stepsWithClientAndUserId = steps.map(step => ({ ...step.toJSON(),
38
45
  clientId,
39
46
  userId
40
47
  }));
48
+ const start = new Date().getTime();
41
49
  channel.broadcast('steps:commit', {
42
50
  steps: stepsWithClientAndUserId,
43
51
  version,
44
52
  userId
53
+ }, response => {
54
+ const latency = new Date().getTime() - start;
55
+
56
+ if (response.type === AcknowledgementResponseTypes.SUCCESS) {
57
+ onStepsAdded({
58
+ steps: stepsWithClientAndUserId,
59
+ version: response.version
60
+ }, true);
61
+ let analyticStepEvent = {
62
+ eventAction: EVENT_ACTION.ADD_STEPS,
63
+ attributes: {
64
+ eventStatus: EVENT_STATUS.SUCCESS,
65
+ type: ADD_STEPS_TYPE.ACCEPTED,
66
+ documentAri,
67
+ latency
68
+ }
69
+ };
70
+ analyticStepEvent.attributes.stepType = countBy(stepsWithClientAndUserId, stepWithClientAndUserId => stepWithClientAndUserId.stepType);
71
+ triggerAnalyticsEvent(analyticStepEvent, analyticsClient);
72
+ } else if (response.type === AcknowledgementResponseTypes.ERROR) {
73
+ var _response$error, _response$error$data, _response$error2, _response$error2$data;
74
+
75
+ onErrorHandled(response.error, true);
76
+ triggerAnalyticsEvent({
77
+ eventAction: EVENT_ACTION.ADD_STEPS,
78
+ attributes: {
79
+ eventStatus: EVENT_STATUS.FAILURE,
80
+ // User tried committing steps but they were rejected because:
81
+ // - 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
82
+ // - VERSION_NUMBER_ALREADY_EXISTS: while storing the steps there was a conflict meaning someone else wrote steps into the database more quickly
83
+ type: ((_response$error = response.error) === null || _response$error === void 0 ? void 0 : (_response$error$data = _response$error.data) === null || _response$error$data === void 0 ? void 0 : _response$error$data.code) === 'HEAD_VERSION_UPDATE_FAILED' || ((_response$error2 = response.error) === null || _response$error2 === void 0 ? void 0 : (_response$error2$data = _response$error2.data) === null || _response$error2$data === void 0 ? void 0 : _response$error2$data.code) === 'VERSION_NUMBER_ALREADY_EXISTS' ? ADD_STEPS_TYPE.REJECTED : ADD_STEPS_TYPE.ERROR,
84
+ documentAri,
85
+ latency,
86
+ error: response.error
87
+ }
88
+ }, analyticsClient);
89
+ } else {
90
+ triggerAnalyticsEvent({
91
+ eventAction: EVENT_ACTION.ADD_STEPS,
92
+ attributes: {
93
+ eventStatus: EVENT_STATUS.FAILURE,
94
+ type: ADD_STEPS_TYPE.ERROR,
95
+ documentAri,
96
+ latency,
97
+ error: {
98
+ message: 'Invalid Acknowledgement'
99
+ }
100
+ }
101
+ }, analyticsClient);
102
+ }
45
103
  });
46
104
  };
47
105
 
@@ -99,16 +157,16 @@ export class Provider extends Emitter {
99
157
  version,
100
158
  metadata
101
159
  }) => {
102
- // Preseve the unconfirmed steps to prevent data loss.
160
+ // Preserve the unconfirmed steps to prevent data loss.
103
161
  const {
104
162
  steps: unconfirmedSteps
105
163
  } = this.getUnconfirmedSteps() || {
106
164
  steps: []
107
165
  }; // Reset the editor,
108
- // - Repalce the document, keep in sync with the server
109
- // - Repalce the version number, so editor is in sync with NCS server and can commit new changes.
110
- // - Repalce the metadata
111
- // - Reserve the cursore position, in case a cursor jump.
166
+ // - Replace the document, keep in sync with the server
167
+ // - Replace the version number, so editor is in sync with NCS server and can commit new changes.
168
+ // - Replace the metadata
169
+ // - Reserve the cursor position, in case a cursor jump.
112
170
 
113
171
  this.updateDocumentWithMetadata({
114
172
  doc,
@@ -116,7 +174,7 @@ export class Provider extends Emitter {
116
174
  metadata,
117
175
  reserveCursor: true
118
176
  });
119
- triggerCollabAnalyticsEvent({
177
+ triggerAnalyticsEvent({
120
178
  eventAction: EVENT_ACTION.REINITIALISE_DOCUMENT,
121
179
  attributes: {
122
180
  numUnconfirmedSteps: unconfirmedSteps.length,
@@ -125,11 +183,11 @@ export class Provider extends Emitter {
125
183
  }, this.analyticsClient); // Re-apply the unconfirmed steps, not 100% of them can be applied, if document is changed significantly.
126
184
 
127
185
  if (unconfirmedSteps.length > 0) {
128
- this.applyLocalsteps(unconfirmedSteps);
186
+ this.applyLocalSteps(unconfirmedSteps);
129
187
  }
130
188
  });
131
189
 
132
- _defineProperty(this, "onStepsAdded", data => {
190
+ _defineProperty(this, "onStepsAdded", (data, disableAnalytics = false) => {
133
191
  logger(`Received steps`, {
134
192
  steps: data.steps,
135
193
  version: data.version
@@ -146,7 +204,7 @@ export class Provider extends Emitter {
146
204
  if (data.version === currentVersion) {
147
205
  logger(`Received steps we already have. Ignoring.`);
148
206
  } else if (data.version === expectedVersion) {
149
- this.processSteps(data);
207
+ this.processSteps(data, disableAnalytics);
150
208
  } else if (data.version > expectedVersion) {
151
209
  logger(`Version too high. Expected "${expectedVersion}" but got "${data.version}. Current local version is ${currentVersion}.`);
152
210
  this.queueSteps(data);
@@ -163,7 +221,7 @@ export class Provider extends Emitter {
163
221
  trailing: true
164
222
  }));
165
223
 
166
- _defineProperty(this, "fitlerQueue", condition => {
224
+ _defineProperty(this, "filterQueue", condition => {
167
225
  this.queue = this.queue.filter(condition);
168
226
  });
169
227
 
@@ -188,7 +246,7 @@ export class Provider extends Emitter {
188
246
  }
189
247
  });
190
248
 
191
- _defineProperty(this, "applyLocalsteps", steps => {
249
+ _defineProperty(this, "applyLocalSteps", steps => {
192
250
  // Re-aply local steps
193
251
  this.emit('local-steps', {
194
252
  steps
@@ -204,7 +262,7 @@ export class Provider extends Emitter {
204
262
  });
205
263
 
206
264
  _defineProperty(this, "catchup", async () => {
207
- startMeasure(MEASURE_NAME.CALLING_CATCHUP_API); // if the queue is already paused, we are busy with something else, so don't proceed.
265
+ const start = new Date().getTime(); // if the queue is already paused, we are busy with something else, so don't proceed.
208
266
 
209
267
  if (this.pauseQueue) {
210
268
  logger(`Queue is paused. Aborting.`);
@@ -218,27 +276,27 @@ export class Provider extends Emitter {
218
276
  getCurrentPmVersion: this.getCurrentPmVersion,
219
277
  fetchCatchup: this.channel.fetchCatchup.bind(this.channel),
220
278
  getUnconfirmedSteps: this.getUnconfirmedSteps,
221
- fitlerQueue: this.fitlerQueue,
279
+ filterQueue: this.filterQueue,
222
280
  updateDocumentWithMetadata: this.updateDocumentWithMetadata,
223
- applyLocalsteps: this.applyLocalsteps
281
+ applyLocalSteps: this.applyLocalSteps
224
282
  });
225
- const measure = stopMeasure(MEASURE_NAME.CALLING_CATCHUP_API);
226
- triggerCollabAnalyticsEvent({
283
+ const latency = new Date().getTime() - start;
284
+ triggerAnalyticsEvent({
227
285
  eventAction: EVENT_ACTION.CATCHUP,
228
286
  attributes: {
229
287
  eventStatus: EVENT_STATUS.SUCCESS,
230
- latency: measure === null || measure === void 0 ? void 0 : measure.duration,
288
+ latency,
231
289
  documentAri: this.config.documentAri
232
290
  }
233
291
  }, this.analyticsClient);
234
292
  } catch (error) {
235
- const measure = stopMeasure(MEASURE_NAME.CALLING_CATCHUP_API);
236
- triggerCollabAnalyticsEvent({
293
+ const latency = new Date().getTime() - start;
294
+ triggerAnalyticsEvent({
237
295
  eventAction: EVENT_ACTION.CATCHUP,
238
296
  attributes: {
239
297
  eventStatus: EVENT_STATUS.FAILURE,
240
298
  error: error,
241
- latency: measure === null || measure === void 0 ? void 0 : measure.duration,
299
+ latency,
242
300
  documentAri: this.config.documentAri
243
301
  }
244
302
  }, this.analyticsClient);
@@ -251,27 +309,32 @@ export class Provider extends Emitter {
251
309
  }
252
310
  });
253
311
 
254
- _defineProperty(this, "onErrorHandled", error => {
255
- if (error && error.data) {
312
+ _defineProperty(this, "onErrorHandled", (error, disableAnalytics = false) => {
313
+ if (error !== null && error !== void 0 && error.data) {
314
+ // User tried committing steps but they were rejected because:
315
+ // 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
316
+ // VERSION_NUMBER_ALREADY_EXISTS: while storing the steps there was a conflict meaning someone else wrote steps into the database more quickly
256
317
  if (error.data.code === 'HEAD_VERSION_UPDATE_FAILED' || error.data.code === 'VERSION_NUMBER_ALREADY_EXISTS') {
257
- const measure = stopMeasure(MEASURE_NAME.ADD_STEPS);
258
- triggerCollabAnalyticsEvent({
259
- eventAction: EVENT_ACTION.ADD_STEPS,
260
- attributes: {
261
- eventStatus: EVENT_STATUS.FAILURE,
262
- error,
263
- documentAri: this.config.documentAri,
264
- latency: measure === null || measure === void 0 ? void 0 : measure.duration
265
- }
266
- }, this.analyticsClient);
267
- this.stepRejectCounter++;
268
- }
318
+ // TODO: Remove this analytics logic once we have validated the ack messages and aren't likely to go back to a generic error handler
319
+ if (!disableAnalytics) {
320
+ triggerAnalyticsEvent({
321
+ eventAction: EVENT_ACTION.ADD_STEPS,
322
+ attributes: {
323
+ eventStatus: EVENT_STATUS.FAILURE,
324
+ type: ADD_STEPS_TYPE.REJECTED,
325
+ error,
326
+ documentAri: this.config.documentAri
327
+ }
328
+ }, this.analyticsClient);
329
+ }
269
330
 
270
- logger(`The stepRejectCounter("${this.stepRejectCounter}")`);
331
+ this.stepRejectCounter++;
332
+ logger(`Steps rejected (tries=${this.stepRejectCounter})`);
271
333
 
272
- if (this.stepRejectCounter >= MAX_STEP_REJECTED_ERROR) {
273
- logger(`The stepRejected("${this.stepRejectCounter}") exceed maximun("${MAX_STEP_REJECTED_ERROR}"), trigger catch`);
274
- this.throttledCatchup();
334
+ if (this.stepRejectCounter >= MAX_STEP_REJECTED_ERROR) {
335
+ logger(`The steps were rejected too many times (tries=${this.stepRejectCounter}, limit=${MAX_STEP_REJECTED_ERROR}). Trying to catch-up.`);
336
+ this.throttledCatchup();
337
+ }
275
338
  }
276
339
 
277
340
  const errorToEmit = errorCodeMapper(error);
@@ -281,7 +344,7 @@ export class Provider extends Emitter {
281
344
  }
282
345
  }
283
346
 
284
- logger(`Error from collab service`, error);
347
+ logger('Error from collab service', error);
285
348
  });
286
349
 
287
350
  _defineProperty(this, "queue", []);
@@ -317,7 +380,7 @@ export class Provider extends Emitter {
317
380
  });
318
381
 
319
382
  _defineProperty(this, "onMetadataChanged", metadata => {
320
- if (metadata !== undefined && !isequal(this.metadata, metadata)) {
383
+ if (metadata !== undefined && !isEqual(this.metadata, metadata)) {
321
384
  this.metadata = metadata;
322
385
  this.emit('metadata:changed', metadata);
323
386
  }
@@ -441,7 +504,7 @@ export class Provider extends Emitter {
441
504
  if (joined.length || left.length) {
442
505
  var _this$participants$si;
443
506
 
444
- triggerCollabAnalyticsEvent({
507
+ triggerAnalyticsEvent({
445
508
  eventAction: EVENT_ACTION.UPDATE_PARTICIPANTS,
446
509
  attributes: {
447
510
  participants: (_this$participants$si = this.participants.size) !== null && _this$participants$si !== void 0 ? _this$participants$si : 1,
@@ -542,7 +605,7 @@ export class Provider extends Emitter {
542
605
  }
543
606
 
544
607
  const measure = stopMeasure(MEASURE_NAME.COMMIT_UNCONFIRMED_STEPS);
545
- triggerCollabAnalyticsEvent({
608
+ triggerAnalyticsEvent({
546
609
  eventAction: EVENT_ACTION.COMMIT_UNCONFIRMED_STEPS,
547
610
  attributes: {
548
611
  eventStatus: EVENT_STATUS.FAILURE,
@@ -557,7 +620,7 @@ export class Provider extends Emitter {
557
620
  }
558
621
 
559
622
  const measure = stopMeasure(MEASURE_NAME.COMMIT_UNCONFIRMED_STEPS);
560
- triggerCollabAnalyticsEvent({
623
+ triggerAnalyticsEvent({
561
624
  eventAction: EVENT_ACTION.COMMIT_UNCONFIRMED_STEPS,
562
625
  attributes: {
563
626
  eventStatus: EVENT_STATUS.SUCCESS,
@@ -577,7 +640,7 @@ export class Provider extends Emitter {
577
640
  startMeasure(MEASURE_NAME.CONVERT_PM_TO_ADF);
578
641
  adfDocument = new JSONTransformer().encode(state.doc);
579
642
  const measure = stopMeasure(MEASURE_NAME.CONVERT_PM_TO_ADF);
580
- triggerCollabAnalyticsEvent({
643
+ triggerAnalyticsEvent({
581
644
  eventAction: EVENT_ACTION.CONVERT_PM_TO_ADF,
582
645
  attributes: {
583
646
  eventStatus: EVENT_STATUS.SUCCESS,
@@ -587,7 +650,7 @@ export class Provider extends Emitter {
587
650
  }, this.analyticsClient);
588
651
  } catch (error) {
589
652
  const measure = stopMeasure(MEASURE_NAME.CONVERT_PM_TO_ADF);
590
- triggerCollabAnalyticsEvent({
653
+ triggerAnalyticsEvent({
591
654
  eventAction: EVENT_ACTION.CONVERT_PM_TO_ADF,
592
655
  attributes: {
593
656
  eventStatus: EVENT_STATUS.FAILURE,
@@ -713,18 +776,21 @@ export class Provider extends Emitter {
713
776
 
714
777
  if (!steps || !steps.length) {
715
778
  return;
716
- }
717
-
718
- startMeasure(MEASURE_NAME.ADD_STEPS); // Avoid reference issues using a
779
+ } // Avoid reference issues using a
719
780
  // method outside of the provider
720
781
  // scope
721
782
 
783
+
722
784
  throttledCommitStep({
723
785
  channel: this.channel,
724
786
  userId: this.userId,
725
787
  clientId: this.clientId,
726
788
  steps,
727
- version
789
+ version,
790
+ documentAri: this.config.documentAri,
791
+ analyticsClient: this.analyticsClient,
792
+ onStepsAdded: this.onStepsAdded.bind(this),
793
+ onErrorHandled: this.onErrorHandled.bind(this)
728
794
  });
729
795
  } // Triggered when page recovery has emitted an 'init' event on a page client is currently connected to.
730
796
 
@@ -757,14 +823,14 @@ export class Provider extends Emitter {
757
823
  }
758
824
  }
759
825
 
760
- processSteps(data) {
826
+ processSteps(data, disableAnalytics = false) {
761
827
  const {
762
828
  version,
763
829
  steps
764
830
  } = data;
765
831
  logger(`Processing data. Version "${version}".`);
766
832
 
767
- if (steps && steps.length) {
833
+ if (steps !== null && steps !== void 0 && steps.length) {
768
834
  const clientIds = steps.map(({
769
835
  clientId
770
836
  }) => clientId);
@@ -774,16 +840,21 @@ export class Provider extends Emitter {
774
840
  userIds: clientIds
775
841
  }); // If steps can apply to local editor successfully, no need to accumulate the error counter.
776
842
 
777
- this.stepRejectCounter = 0;
778
- const measure = stopMeasure(MEASURE_NAME.ADD_STEPS);
779
- triggerCollabAnalyticsEvent({
780
- eventAction: EVENT_ACTION.ADD_STEPS,
781
- attributes: {
782
- eventStatus: EVENT_STATUS.SUCCESS,
783
- documentAri: this.config.documentAri,
784
- latency: measure === null || measure === void 0 ? void 0 : measure.duration
785
- }
786
- }, this.analyticsClient);
843
+ this.stepRejectCounter = 0; // TODO: Remove this analytics logic after we've validated the ack call-backs
844
+
845
+ if (!disableAnalytics && clientIds.indexOf(this.clientId) >= 0) {
846
+ let analyticStepEvent = {
847
+ eventAction: EVENT_ACTION.ADD_STEPS,
848
+ attributes: {
849
+ eventStatus: EVENT_STATUS.SUCCESS,
850
+ type: ADD_STEPS_TYPE.ACCEPTED,
851
+ documentAri: this.config.documentAri
852
+ }
853
+ };
854
+ analyticStepEvent.attributes.stepType = countBy(steps, step => step.stepType);
855
+ triggerAnalyticsEvent(analyticStepEvent, this.analyticsClient);
856
+ }
857
+
787
858
  this.emitTelepointersFromSteps(steps); // Resend local steps if none of the received steps originated with us!
788
859
 
789
860
  if (clientIds.indexOf(this.clientId) === -1) {
@@ -813,6 +884,21 @@ export class Provider extends Emitter {
813
884
 
814
885
  switch (type) {
815
886
  case 'telepointer':
887
+ const telepointerExperience = new UFOExperience('collab-provider.telepointer', {
888
+ type: ExperienceTypes.Operation,
889
+ performanceType: ExperiencePerformanceTypes.Custom,
890
+ performanceConfig: {
891
+ histogram: {
892
+ [ExperiencePerformanceTypes.Custom]: {
893
+ duration: '250_500_1000_1500_2000_3000_4000'
894
+ }
895
+ }
896
+ }
897
+ });
898
+ telepointerExperience.addMetadata({
899
+ documentAri: this.config.documentAri
900
+ });
901
+ telepointerExperience.start();
816
902
  const {
817
903
  selection
818
904
  } = rest;
@@ -821,6 +907,20 @@ export class Provider extends Emitter {
821
907
  userId: userId,
822
908
  sessionId: sessionId,
823
909
  clientId: clientId
910
+ }, response => {
911
+ if (response.type === AcknowledgementResponseTypes.SUCCESS) {
912
+ telepointerExperience.success();
913
+ } else if (response.type === AcknowledgementResponseTypes.ERROR) {
914
+ const errorMessage = response.error;
915
+ telepointerExperience.addMetadata({
916
+ error: errorMessage
917
+ });
918
+ logger('Error from collab service with telepointer broadcast', errorMessage);
919
+ telepointerExperience.failure();
920
+ } else {
921
+ // Abort if invalid ACK sent
922
+ telepointerExperience.abort();
923
+ }
824
924
  });
825
925
  break;
826
926
  }
@@ -1 +1,7 @@
1
- export {};
1
+ // Channel
2
+ export let AcknowledgementResponseTypes;
3
+
4
+ (function (AcknowledgementResponseTypes) {
5
+ AcknowledgementResponseTypes["SUCCESS"] = "SUCCESS";
6
+ AcknowledgementResponseTypes["ERROR"] = "ERROR";
7
+ })(AcknowledgementResponseTypes || (AcknowledgementResponseTypes = {}));
@@ -1,5 +1,5 @@
1
1
  export const name = "@atlaskit/collab-provider";
2
- export const version = "7.7.0";
2
+ export const version = "8.0.0";
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": "7.7.0",
3
+ "version": "8.0.0",
4
4
  "sideEffects": false
5
5
  }
@@ -6,35 +6,28 @@ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { va
6
6
 
7
7
  import { EVENT_SUBJECT, COLLAB_SERVICE } from '../helpers/const';
8
8
  import { name as packageName, version as packageVersion } from '../version-wrapper';
9
- export var fireAnalyticsEvent = function fireAnalyticsEvent(analyticsClient, payload) {
10
- if (!analyticsClient || !payload) {
9
+ export var triggerAnalyticsEvent = function triggerAnalyticsEvent(analyticsEvent, analyticsClient) {
10
+ if (!analyticsClient) {
11
11
  return;
12
12
  }
13
13
 
14
- var client = analyticsClient;
15
- var requestIdleCallbackFunction = window.requestIdleCallback;
16
- var runItLater = typeof requestIdleCallbackFunction === 'function' ? requestIdleCallbackFunction : window.requestAnimationFrame; // Let the browser figure out
17
- // when it should send those events
18
-
19
- runItLater(function () {
20
- client.sendOperationalEvent(_objectSpread(_objectSpread({
21
- action: 'collab'
22
- }, payload), {}, {
23
- source: payload.source || 'unknown',
24
- tags: ['editor']
25
- }));
26
- });
27
- };
28
- export var triggerCollabAnalyticsEvent = function triggerCollabAnalyticsEvent(analyticsEvent, analyticsClient) {
29
14
  var payload = {
30
- action: analyticsEvent.eventAction,
31
15
  actionSubject: EVENT_SUBJECT,
32
- source: 'unknown',
33
16
  attributes: _objectSpread({
34
17
  packageName: packageName,
35
18
  packageVersion: packageVersion,
36
19
  collabService: COLLAB_SERVICE.NCS
37
- }, analyticsEvent.attributes)
38
- };
39
- fireAnalyticsEvent(analyticsClient, payload);
20
+ }, analyticsEvent.attributes),
21
+ tags: ['editor'],
22
+ action: analyticsEvent.eventAction,
23
+ source: 'unknown' // Adds zero analytics value, but event validation throws an error if you don't add it :-(
24
+
25
+ }; // Let the browser figure out
26
+ // when it should send those events
27
+
28
+ var requestIdleCallbackFunction = window.requestIdleCallback;
29
+ var runItLater = typeof requestIdleCallbackFunction === 'function' ? requestIdleCallbackFunction : window.requestAnimationFrame;
30
+ runItLater(function () {
31
+ analyticsClient.sendOperationalEvent(payload);
32
+ });
40
33
  };
@@ -1,11 +1,9 @@
1
1
  export var MEASURE_NAME;
2
2
 
3
3
  (function (MEASURE_NAME) {
4
- MEASURE_NAME["CALLING_CATCHUP_API"] = "callingCatchupApi";
5
4
  MEASURE_NAME["SOCKET_CONNECT"] = "socketConnect";
6
5
  MEASURE_NAME["DOCUMENT_INIT"] = "documentInit";
7
6
  MEASURE_NAME["CONVERT_PM_TO_ADF"] = "convertPMToADF";
8
- MEASURE_NAME["ADD_STEPS"] = "addSteps";
9
7
  MEASURE_NAME["COMMIT_UNCONFIRMED_STEPS"] = "commitUnconfirmedSteps";
10
8
  })(MEASURE_NAME || (MEASURE_NAME = {}));
11
9