@atlaskit/collab-provider 8.0.1 → 8.2.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.
- package/CHANGELOG.md +27 -0
- package/dist/cjs/analytics/index.js +2 -11
- package/dist/cjs/analytics/performance.js +2 -16
- package/dist/cjs/channel.js +66 -115
- package/dist/cjs/disconnected-reason-mapper.js +0 -2
- package/dist/cjs/emitter.js +3 -11
- package/dist/cjs/error-code-mapper.js +4 -17
- package/dist/cjs/feature-flags/__test__/index.unit.js +27 -0
- package/dist/cjs/feature-flags/index.js +52 -0
- package/dist/cjs/feature-flags/types.js +12 -0
- package/dist/cjs/helpers/const.js +6 -10
- package/dist/cjs/helpers/utils.js +0 -12
- package/dist/cjs/index.js +0 -1
- package/dist/cjs/provider/catchup.js +38 -42
- package/dist/cjs/provider/index.js +153 -283
- package/dist/cjs/socket-io-provider.js +2 -12
- package/dist/cjs/types.js +0 -1
- package/dist/cjs/version-wrapper.js +1 -3
- package/dist/cjs/version.json +1 -1
- package/dist/es2019/analytics/index.js +2 -3
- package/dist/es2019/analytics/performance.js +2 -13
- package/dist/es2019/channel.js +57 -65
- package/dist/es2019/disconnected-reason-mapper.js +1 -2
- package/dist/es2019/emitter.js +3 -8
- package/dist/es2019/error-code-mapper.js +4 -12
- package/dist/es2019/feature-flags/__test__/index.unit.js +25 -0
- package/dist/es2019/feature-flags/index.js +31 -0
- package/dist/es2019/feature-flags/types.js +5 -0
- package/dist/es2019/helpers/const.js +4 -9
- package/dist/es2019/helpers/utils.js +0 -2
- package/dist/es2019/provider/catchup.js +33 -17
- package/dist/es2019/provider/index.js +119 -189
- package/dist/es2019/socket-io-provider.js +4 -2
- package/dist/es2019/types.js +1 -1
- package/dist/es2019/version-wrapper.js +1 -1
- package/dist/es2019/version.json +1 -1
- package/dist/esm/analytics/index.js +2 -6
- package/dist/esm/analytics/performance.js +2 -13
- package/dist/esm/channel.js +68 -108
- package/dist/esm/disconnected-reason-mapper.js +1 -2
- package/dist/esm/emitter.js +3 -6
- package/dist/esm/error-code-mapper.js +4 -12
- package/dist/esm/feature-flags/__test__/index.unit.js +25 -0
- package/dist/esm/feature-flags/index.js +43 -0
- package/dist/esm/feature-flags/types.js +5 -0
- package/dist/esm/helpers/const.js +4 -9
- package/dist/esm/helpers/utils.js +0 -3
- package/dist/esm/provider/catchup.js +38 -35
- package/dist/esm/provider/index.js +153 -285
- package/dist/esm/socket-io-provider.js +2 -5
- package/dist/esm/types.js +1 -1
- package/dist/esm/version-wrapper.js +1 -1
- package/dist/esm/version.json +1 -1
- package/dist/types/channel.d.ts +1 -0
- package/dist/types/error-code-mapper.d.ts +1 -1
- package/dist/types/feature-flags/__test__/index.unit.d.ts +1 -0
- package/dist/types/feature-flags/index.d.ts +9 -0
- package/dist/types/feature-flags/types.d.ts +11 -0
- package/dist/types/helpers/const.d.ts +19 -3
- package/dist/types/provider/catchup.d.ts +1 -1
- package/dist/types/provider/index.d.ts +1 -0
- package/dist/types/types.d.ts +9 -4
- package/package.json +10 -9
- package/report.api.md +11 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
2
|
-
import { getVersion, sendableSteps } from 'prosemirror-collab';
|
|
2
|
+
import { getVersion, sendableSteps } from '@atlaskit/prosemirror-collab';
|
|
3
3
|
import throttle from 'lodash/throttle';
|
|
4
4
|
import isEqual from 'lodash/isEqual';
|
|
5
5
|
import countBy from 'lodash/countBy';
|
|
@@ -17,19 +17,12 @@ import { DisconnectReason, socketIOReasons } from '../disconnected-reason-mapper
|
|
|
17
17
|
import { MEASURE_NAME, startMeasure, stopMeasure } from '../analytics/performance';
|
|
18
18
|
const logger = createLogger('Provider', 'black');
|
|
19
19
|
const PARTICIPANT_UPDATE_INTERVAL = 300 * 1000; // 300 seconds
|
|
20
|
-
|
|
21
20
|
const SEND_PRESENCE_INTERVAL = 150 * 1000; // 150 seconds
|
|
22
|
-
|
|
23
21
|
const SEND_STEPS_THROTTLE = 500; // 0.5 second
|
|
24
|
-
|
|
25
22
|
export const CATCHUP_THROTTLE = 1 * 1000; // 1 second
|
|
26
|
-
|
|
27
23
|
const OUT_OF_SYNC_PERIOD = 3 * 1000; // 3 seconds
|
|
28
|
-
|
|
29
24
|
const noop = () => {};
|
|
30
|
-
|
|
31
25
|
export const MAX_STEP_REJECTED_ERROR = 15;
|
|
32
|
-
|
|
33
26
|
const commitStep = ({
|
|
34
27
|
channel,
|
|
35
28
|
steps,
|
|
@@ -41,7 +34,8 @@ const commitStep = ({
|
|
|
41
34
|
onStepsAdded,
|
|
42
35
|
onErrorHandled
|
|
43
36
|
}) => {
|
|
44
|
-
const stepsWithClientAndUserId = steps.map(step => ({
|
|
37
|
+
const stepsWithClientAndUserId = steps.map(step => ({
|
|
38
|
+
...step.toJSON(),
|
|
45
39
|
clientId,
|
|
46
40
|
userId
|
|
47
41
|
}));
|
|
@@ -52,7 +46,6 @@ const commitStep = ({
|
|
|
52
46
|
userId
|
|
53
47
|
}, response => {
|
|
54
48
|
const latency = new Date().getTime() - start;
|
|
55
|
-
|
|
56
49
|
if (response.type === AcknowledgementResponseTypes.SUCCESS) {
|
|
57
50
|
onStepsAdded({
|
|
58
51
|
steps: stepsWithClientAndUserId,
|
|
@@ -71,7 +64,6 @@ const commitStep = ({
|
|
|
71
64
|
triggerAnalyticsEvent(analyticStepEvent, analyticsClient);
|
|
72
65
|
} else if (response.type === AcknowledgementResponseTypes.ERROR) {
|
|
73
66
|
var _response$error, _response$error$data, _response$error2, _response$error2$data;
|
|
74
|
-
|
|
75
67
|
onErrorHandled(response.error, true);
|
|
76
68
|
triggerAnalyticsEvent({
|
|
77
69
|
eventAction: EVENT_ACTION.ADD_STEPS,
|
|
@@ -102,41 +94,39 @@ const commitStep = ({
|
|
|
102
94
|
}
|
|
103
95
|
});
|
|
104
96
|
};
|
|
105
|
-
|
|
106
97
|
const throttledCommitStep = throttle(commitStep, SEND_STEPS_THROTTLE, {
|
|
107
98
|
leading: false,
|
|
108
99
|
trailing: true
|
|
109
100
|
});
|
|
110
101
|
export class Provider extends Emitter {
|
|
111
102
|
// To keep track of the namespace event changes from the server.
|
|
103
|
+
|
|
112
104
|
constructor(config) {
|
|
113
105
|
super();
|
|
114
|
-
|
|
115
106
|
_defineProperty(this, "participants", new Map());
|
|
116
|
-
|
|
117
107
|
_defineProperty(this, "metadata", {});
|
|
118
|
-
|
|
119
108
|
_defineProperty(this, "stepRejectCounter", 0);
|
|
120
|
-
|
|
121
109
|
_defineProperty(this, "isChannelInitialized", false);
|
|
122
|
-
|
|
123
110
|
_defineProperty(this, "isNamespaceLocked", false);
|
|
124
|
-
|
|
125
111
|
_defineProperty(this, "initializeChannel", () => {
|
|
112
|
+
this.emit('connecting', {
|
|
113
|
+
initial: true
|
|
114
|
+
});
|
|
126
115
|
this.channel.on('connected', ({
|
|
127
116
|
sid,
|
|
128
117
|
initialized
|
|
129
118
|
}) => {
|
|
130
119
|
this.sessionId = sid;
|
|
131
120
|
this.emit('connected', {
|
|
132
|
-
sid
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
121
|
+
sid,
|
|
122
|
+
initial: !initialized
|
|
123
|
+
});
|
|
124
|
+
// If already initialized, `connected` means reconnected
|
|
125
|
+
if (initialized && this.disconnectedAt &&
|
|
126
|
+
// Offline longer than `OUT_OF_SYNC_PERIOD`
|
|
136
127
|
Date.now() - this.disconnectedAt >= OUT_OF_SYNC_PERIOD) {
|
|
137
128
|
this.throttledCatchup();
|
|
138
129
|
}
|
|
139
|
-
|
|
140
130
|
this.disconnectedAt = undefined;
|
|
141
131
|
}).on('init', ({
|
|
142
132
|
doc,
|
|
@@ -151,7 +141,6 @@ export class Provider extends Emitter {
|
|
|
151
141
|
});
|
|
152
142
|
}).on('restore', this.onRestore.bind(this)).on('steps:added', this.onStepsAdded.bind(this)).on('participant:telepointer', this.onParticipantTelepointer.bind(this)).on('presence:joined', this.onPresenceJoined.bind(this)).on('presence', this.onPresence.bind(this)).on('participant:left', this.onParticipantLeft.bind(this)).on('participant:updated', this.onParticipantUpdated.bind(this)).on('metadata:changed', this.onMetadataChanged.bind(this)).on('disconnect', this.onDisconnected.bind(this)).on('error', this.onErrorHandled.bind(this)).on('status', this.onNamespaceStatusChanged.bind(this)).connect();
|
|
153
143
|
});
|
|
154
|
-
|
|
155
144
|
_defineProperty(this, "onRestore", ({
|
|
156
145
|
doc,
|
|
157
146
|
version,
|
|
@@ -162,12 +151,13 @@ export class Provider extends Emitter {
|
|
|
162
151
|
steps: unconfirmedSteps
|
|
163
152
|
} = this.getUnconfirmedSteps() || {
|
|
164
153
|
steps: []
|
|
165
|
-
};
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
// Reset the editor,
|
|
166
157
|
// - Replace the document, keep in sync with the server
|
|
167
158
|
// - Replace the version number, so editor is in sync with NCS server and can commit new changes.
|
|
168
159
|
// - Replace the metadata
|
|
169
160
|
// - Reserve the cursor position, in case a cursor jump.
|
|
170
|
-
|
|
171
161
|
this.updateDocumentWithMetadata({
|
|
172
162
|
doc,
|
|
173
163
|
version,
|
|
@@ -180,27 +170,24 @@ export class Provider extends Emitter {
|
|
|
180
170
|
numUnconfirmedSteps: unconfirmedSteps.length,
|
|
181
171
|
documentAri: this.config.documentAri
|
|
182
172
|
}
|
|
183
|
-
}, this.analyticsClient);
|
|
173
|
+
}, this.analyticsClient);
|
|
184
174
|
|
|
175
|
+
// Re-apply the unconfirmed steps, not 100% of them can be applied, if document is changed significantly.
|
|
185
176
|
if (unconfirmedSteps.length > 0) {
|
|
186
177
|
this.applyLocalSteps(unconfirmedSteps);
|
|
187
178
|
}
|
|
188
179
|
});
|
|
189
|
-
|
|
190
180
|
_defineProperty(this, "onStepsAdded", (data, disableAnalytics = false) => {
|
|
191
181
|
logger(`Received steps`, {
|
|
192
182
|
steps: data.steps,
|
|
193
183
|
version: data.version
|
|
194
184
|
});
|
|
195
|
-
|
|
196
185
|
if (!data.steps) {
|
|
197
186
|
logger(`No steps.. waiting..`);
|
|
198
187
|
return;
|
|
199
188
|
}
|
|
200
|
-
|
|
201
189
|
const currentVersion = this.getCurrentPmVersion();
|
|
202
190
|
const expectedVersion = currentVersion + data.steps.length;
|
|
203
|
-
|
|
204
191
|
if (data.version === currentVersion) {
|
|
205
192
|
logger(`Received steps we already have. Ignoring.`);
|
|
206
193
|
} else if (data.version === expectedVersion) {
|
|
@@ -210,21 +197,17 @@ export class Provider extends Emitter {
|
|
|
210
197
|
this.queueSteps(data);
|
|
211
198
|
this.throttledCatchup();
|
|
212
199
|
}
|
|
213
|
-
|
|
214
200
|
this.updateParticipants([], data.steps.map(({
|
|
215
201
|
userId
|
|
216
202
|
}) => userId));
|
|
217
203
|
});
|
|
218
|
-
|
|
219
204
|
_defineProperty(this, "throttledCatchup", throttle(() => this.catchup(), CATCHUP_THROTTLE, {
|
|
220
205
|
leading: false,
|
|
221
206
|
trailing: true
|
|
222
207
|
}));
|
|
223
|
-
|
|
224
208
|
_defineProperty(this, "filterQueue", condition => {
|
|
225
209
|
this.queue = this.queue.filter(condition);
|
|
226
210
|
});
|
|
227
|
-
|
|
228
211
|
_defineProperty(this, "updateDocumentWithMetadata", ({
|
|
229
212
|
doc,
|
|
230
213
|
version,
|
|
@@ -239,38 +222,31 @@ export class Provider extends Emitter {
|
|
|
239
222
|
reserveCursor
|
|
240
223
|
} : {})
|
|
241
224
|
});
|
|
242
|
-
|
|
243
225
|
if (metadata && Object.keys(metadata).length > 0) {
|
|
244
226
|
this.metadata = metadata;
|
|
245
227
|
this.emit('metadata:changed', metadata);
|
|
246
228
|
}
|
|
247
229
|
});
|
|
248
|
-
|
|
249
230
|
_defineProperty(this, "applyLocalSteps", steps => {
|
|
250
|
-
// Re-
|
|
231
|
+
// Re-apply local steps
|
|
251
232
|
this.emit('local-steps', {
|
|
252
233
|
steps
|
|
253
234
|
});
|
|
254
235
|
});
|
|
255
|
-
|
|
256
236
|
_defineProperty(this, "getCurrentPmVersion", () => {
|
|
257
237
|
return getVersion(this.getState());
|
|
258
238
|
});
|
|
259
|
-
|
|
260
239
|
_defineProperty(this, "getUnconfirmedSteps", () => {
|
|
261
240
|
return sendableSteps(this.getState());
|
|
262
241
|
});
|
|
263
|
-
|
|
264
242
|
_defineProperty(this, "catchup", async () => {
|
|
265
|
-
const start = new Date().getTime();
|
|
266
|
-
|
|
243
|
+
const start = new Date().getTime();
|
|
244
|
+
// if the queue is already paused, we are busy with something else, so don't proceed.
|
|
267
245
|
if (this.pauseQueue) {
|
|
268
246
|
logger(`Queue is paused. Aborting.`);
|
|
269
247
|
return;
|
|
270
248
|
}
|
|
271
|
-
|
|
272
249
|
this.pauseQueue = true;
|
|
273
|
-
|
|
274
250
|
try {
|
|
275
251
|
await catchup({
|
|
276
252
|
getCurrentPmVersion: this.getCurrentPmVersion,
|
|
@@ -308,52 +284,54 @@ export class Provider extends Emitter {
|
|
|
308
284
|
this.stepRejectCounter = 0;
|
|
309
285
|
}
|
|
310
286
|
});
|
|
311
|
-
|
|
312
287
|
_defineProperty(this, "onErrorHandled", (error, disableAnalytics = false) => {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
this.stepRejectCounter++;
|
|
332
|
-
logger(`Steps rejected (tries=${this.stepRejectCounter})`);
|
|
333
|
-
|
|
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
|
-
}
|
|
288
|
+
var _error$data, _error$data2;
|
|
289
|
+
// User tried committing steps but they were rejected because:
|
|
290
|
+
// 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
|
|
291
|
+
// VERSION_NUMBER_ALREADY_EXISTS: while storing the steps there was a conflict meaning someone else wrote steps into the database more quickly
|
|
292
|
+
if (((_error$data = error.data) === null || _error$data === void 0 ? void 0 : _error$data.code) === 'HEAD_VERSION_UPDATE_FAILED' || ((_error$data2 = error.data) === null || _error$data2 === void 0 ? void 0 : _error$data2.code) === 'VERSION_NUMBER_ALREADY_EXISTS') {
|
|
293
|
+
// TODO: Remove this analytics logic once we have validated the ack messages and aren't likely to go back to a generic error handler
|
|
294
|
+
if (!disableAnalytics) {
|
|
295
|
+
triggerAnalyticsEvent({
|
|
296
|
+
eventAction: EVENT_ACTION.ADD_STEPS,
|
|
297
|
+
attributes: {
|
|
298
|
+
eventStatus: EVENT_STATUS.FAILURE,
|
|
299
|
+
type: ADD_STEPS_TYPE.REJECTED,
|
|
300
|
+
error,
|
|
301
|
+
documentAri: this.config.documentAri
|
|
302
|
+
}
|
|
303
|
+
}, this.analyticsClient);
|
|
338
304
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
this.
|
|
305
|
+
this.stepRejectCounter++;
|
|
306
|
+
logger(`Steps rejected (tries=${this.stepRejectCounter})`);
|
|
307
|
+
if (this.stepRejectCounter >= MAX_STEP_REJECTED_ERROR) {
|
|
308
|
+
logger(`The steps were rejected too many times (tries=${this.stepRejectCounter}, limit=${MAX_STEP_REJECTED_ERROR}). Trying to catch-up.`);
|
|
309
|
+
this.throttledCatchup();
|
|
310
|
+
}
|
|
311
|
+
} else {
|
|
312
|
+
const mappedError = errorCodeMapper(error);
|
|
313
|
+
// TODO: Remove this analytics logic once we have validated the ack messages and aren't likely to go back to a generic error handler
|
|
314
|
+
if (!disableAnalytics) {
|
|
315
|
+
triggerAnalyticsEvent({
|
|
316
|
+
eventAction: EVENT_ACTION.ERROR,
|
|
317
|
+
attributes: {
|
|
318
|
+
documentAri: this.config.documentAri,
|
|
319
|
+
mappedError
|
|
320
|
+
},
|
|
321
|
+
nonPrivacySafeAttributes: {
|
|
322
|
+
error
|
|
323
|
+
}
|
|
324
|
+
}, this.analyticsClient);
|
|
344
325
|
}
|
|
326
|
+
this.emit('error', mappedError);
|
|
327
|
+
logger('Error from collab service', error);
|
|
345
328
|
}
|
|
346
|
-
|
|
347
|
-
logger('Error from collab service', error);
|
|
348
329
|
});
|
|
349
|
-
|
|
350
330
|
_defineProperty(this, "queue", []);
|
|
351
|
-
|
|
352
331
|
_defineProperty(this, "sendPresence", () => {
|
|
353
332
|
if (this.presenceUpdateTimeout) {
|
|
354
333
|
clearTimeout(this.presenceUpdateTimeout);
|
|
355
334
|
}
|
|
356
|
-
|
|
357
335
|
this.channel.broadcast('participant:updated', {
|
|
358
336
|
sessionId: this.sessionId,
|
|
359
337
|
userId: this.userId,
|
|
@@ -361,15 +339,14 @@ export class Provider extends Emitter {
|
|
|
361
339
|
});
|
|
362
340
|
this.presenceUpdateTimeout = window.setTimeout(() => this.sendPresence(), SEND_PRESENCE_INTERVAL);
|
|
363
341
|
});
|
|
364
|
-
|
|
365
342
|
_defineProperty(this, "onPresenceJoined", ({
|
|
366
343
|
sessionId
|
|
367
344
|
}) => {
|
|
368
|
-
logger('Participant joined with session: ', sessionId);
|
|
345
|
+
logger('Participant joined with session: ', sessionId);
|
|
369
346
|
|
|
347
|
+
// This expose existing users to the newly joined user
|
|
370
348
|
this.sendPresence();
|
|
371
349
|
});
|
|
372
|
-
|
|
373
350
|
_defineProperty(this, "onPresence", ({
|
|
374
351
|
userId
|
|
375
352
|
}) => {
|
|
@@ -378,14 +355,12 @@ export class Provider extends Emitter {
|
|
|
378
355
|
this.sendPresence();
|
|
379
356
|
this.channel.sendPresenceJoined();
|
|
380
357
|
});
|
|
381
|
-
|
|
382
358
|
_defineProperty(this, "onMetadataChanged", metadata => {
|
|
383
359
|
if (metadata !== undefined && !isEqual(this.metadata, metadata)) {
|
|
384
360
|
this.metadata = metadata;
|
|
385
361
|
this.emit('metadata:changed', metadata);
|
|
386
362
|
}
|
|
387
363
|
});
|
|
388
|
-
|
|
389
364
|
_defineProperty(this, "onParticipantLeft", ({
|
|
390
365
|
sessionId
|
|
391
366
|
}) => {
|
|
@@ -397,7 +372,6 @@ export class Provider extends Emitter {
|
|
|
397
372
|
}]
|
|
398
373
|
});
|
|
399
374
|
});
|
|
400
|
-
|
|
401
375
|
_defineProperty(this, "onParticipantUpdated", ({
|
|
402
376
|
sessionId,
|
|
403
377
|
timestamp,
|
|
@@ -411,7 +385,6 @@ export class Provider extends Emitter {
|
|
|
411
385
|
clientId
|
|
412
386
|
});
|
|
413
387
|
});
|
|
414
|
-
|
|
415
388
|
_defineProperty(this, "onParticipantTelepointer", ({
|
|
416
389
|
sessionId,
|
|
417
390
|
timestamp,
|
|
@@ -422,14 +395,14 @@ export class Provider extends Emitter {
|
|
|
422
395
|
if (sessionId === this.sessionId) {
|
|
423
396
|
return;
|
|
424
397
|
}
|
|
398
|
+
const participant = this.participants.get(sessionId);
|
|
425
399
|
|
|
426
|
-
|
|
427
|
-
|
|
400
|
+
// Ignore old telepointer events
|
|
428
401
|
if (participant && participant.lastActive > timestamp) {
|
|
429
402
|
return;
|
|
430
|
-
}
|
|
431
|
-
|
|
403
|
+
}
|
|
432
404
|
|
|
405
|
+
// Set last active
|
|
433
406
|
this.updateParticipant({
|
|
434
407
|
sessionId,
|
|
435
408
|
timestamp,
|
|
@@ -442,7 +415,6 @@ export class Provider extends Emitter {
|
|
|
442
415
|
sessionId
|
|
443
416
|
});
|
|
444
417
|
});
|
|
445
|
-
|
|
446
418
|
_defineProperty(this, "updateParticipant", async ({
|
|
447
419
|
sessionId,
|
|
448
420
|
timestamp,
|
|
@@ -453,7 +425,6 @@ export class Provider extends Emitter {
|
|
|
453
425
|
// If userId does not exsit, does nothing here to prevent duplication.
|
|
454
426
|
return;
|
|
455
427
|
}
|
|
456
|
-
|
|
457
428
|
const {
|
|
458
429
|
getUser
|
|
459
430
|
} = this.config;
|
|
@@ -463,14 +434,12 @@ export class Provider extends Emitter {
|
|
|
463
434
|
avatar = ''
|
|
464
435
|
} = await (getUser ? getUser(userId) : getParticipant(userId));
|
|
465
436
|
const isNewParticipant = !this.participants.has(sessionId);
|
|
466
|
-
|
|
467
437
|
if (isNewParticipant) {
|
|
468
438
|
logger('new Participant updated', {
|
|
469
439
|
name,
|
|
470
440
|
avatar
|
|
471
441
|
});
|
|
472
442
|
}
|
|
473
|
-
|
|
474
443
|
this.participants.set(sessionId, {
|
|
475
444
|
name,
|
|
476
445
|
email,
|
|
@@ -479,31 +448,30 @@ export class Provider extends Emitter {
|
|
|
479
448
|
lastActive: timestamp,
|
|
480
449
|
userId,
|
|
481
450
|
clientId
|
|
482
|
-
});
|
|
451
|
+
});
|
|
483
452
|
|
|
453
|
+
// Collab-plugin expects an array of users that joined.
|
|
484
454
|
this.updateParticipants(isNewParticipant ? [this.participants.get(sessionId)] : []);
|
|
485
455
|
});
|
|
486
|
-
|
|
487
456
|
_defineProperty(this, "updateParticipants", (joined = [], userIds = []) => {
|
|
488
457
|
if (this.participantUpdateTimeout) {
|
|
489
458
|
clearTimeout(this.participantUpdateTimeout);
|
|
490
459
|
}
|
|
491
|
-
|
|
492
460
|
const now = new Date().getTime();
|
|
493
461
|
Array.from(this.participants.values()).forEach(p => {
|
|
494
462
|
if (userIds.indexOf(p.userId) !== -1) {
|
|
495
|
-
this.participants.set(p.sessionId, {
|
|
463
|
+
this.participants.set(p.sessionId, {
|
|
464
|
+
...p,
|
|
496
465
|
lastActive: now
|
|
497
466
|
});
|
|
498
467
|
}
|
|
499
|
-
});
|
|
468
|
+
});
|
|
500
469
|
|
|
470
|
+
// Filter out participants that's been inactive for more than 5 minutes.
|
|
501
471
|
const left = Array.from(this.participants.values()).filter(p => p.sessionId !== this.sessionId && now - p.lastActive > PARTICIPANT_UPDATE_INTERVAL);
|
|
502
472
|
left.forEach(p => this.participants.delete(p.sessionId));
|
|
503
|
-
|
|
504
473
|
if (joined.length || left.length) {
|
|
505
474
|
var _this$participants$si;
|
|
506
|
-
|
|
507
475
|
triggerAnalyticsEvent({
|
|
508
476
|
eventAction: EVENT_ACTION.UPDATE_PARTICIPANTS,
|
|
509
477
|
attributes: {
|
|
@@ -511,7 +479,8 @@ export class Provider extends Emitter {
|
|
|
511
479
|
documentAri: this.config.documentAri
|
|
512
480
|
}
|
|
513
481
|
}, this.analyticsClient);
|
|
514
|
-
this.emit('presence', {
|
|
482
|
+
this.emit('presence', {
|
|
483
|
+
...(joined.length ? {
|
|
515
484
|
joined
|
|
516
485
|
} : {}),
|
|
517
486
|
...(left.length ? {
|
|
@@ -519,32 +488,24 @@ export class Provider extends Emitter {
|
|
|
519
488
|
} : {})
|
|
520
489
|
});
|
|
521
490
|
}
|
|
522
|
-
|
|
523
491
|
this.participantUpdateTimeout = window.setTimeout(() => this.updateParticipants(), PARTICIPANT_UPDATE_INTERVAL);
|
|
524
492
|
});
|
|
525
|
-
|
|
526
493
|
_defineProperty(this, "disconnectedReasonMapper", reason => {
|
|
527
494
|
switch (reason) {
|
|
528
495
|
case socketIOReasons.IO_CLIENT_DISCONNECT:
|
|
529
496
|
return DisconnectReason.CLIENT_DISCONNECT;
|
|
530
|
-
|
|
531
497
|
case socketIOReasons.IO_SERVER_DISCONNECT:
|
|
532
498
|
return DisconnectReason.SERVER_DISCONNECT;
|
|
533
|
-
|
|
534
499
|
case socketIOReasons.TRANSPORT_CLOSED:
|
|
535
500
|
return DisconnectReason.SOCKET_CLOSED;
|
|
536
|
-
|
|
537
501
|
case socketIOReasons.TRANSPORT_ERROR:
|
|
538
502
|
return DisconnectReason.SOCKET_ERROR;
|
|
539
|
-
|
|
540
503
|
case socketIOReasons.PING_TIMEOUT:
|
|
541
504
|
return DisconnectReason.SOCKET_TIMEOUT;
|
|
542
|
-
|
|
543
505
|
default:
|
|
544
506
|
return DisconnectReason.UNKNOWN_DISCONNECT;
|
|
545
507
|
}
|
|
546
508
|
});
|
|
547
|
-
|
|
548
509
|
_defineProperty(this, "onDisconnected", ({
|
|
549
510
|
reason
|
|
550
511
|
}) => {
|
|
@@ -555,43 +516,36 @@ export class Provider extends Emitter {
|
|
|
555
516
|
reason: this.disconnectedReasonMapper(reason),
|
|
556
517
|
sid: this.sessionId
|
|
557
518
|
});
|
|
558
|
-
|
|
559
519
|
if (left.length) {
|
|
560
520
|
this.emit('presence', {
|
|
561
521
|
left
|
|
562
522
|
});
|
|
563
523
|
}
|
|
564
524
|
});
|
|
565
|
-
|
|
566
525
|
_defineProperty(this, "getFinalAcknowledgedState", async () => {
|
|
567
526
|
var _this$metadata$title;
|
|
568
|
-
|
|
569
527
|
const maxAttemptsToSync = ACK_MAX_TRY;
|
|
570
528
|
let count = 0;
|
|
571
529
|
let unconfirmedState = this.getUnconfirmedSteps();
|
|
572
|
-
|
|
573
530
|
if (unconfirmedState && unconfirmedState.steps.length) {
|
|
574
|
-
startMeasure(MEASURE_NAME.COMMIT_UNCONFIRMED_STEPS);
|
|
531
|
+
startMeasure(MEASURE_NAME.COMMIT_UNCONFIRMED_STEPS);
|
|
532
|
+
// We use origins here as steps can be rebased. When steps are rebased a new step is created.
|
|
575
533
|
// This means that we can not track if it has been removed from the unconfirmed array or not.
|
|
576
534
|
// Origins points to the original transaction that the step was created in. This is never changed
|
|
577
535
|
// and gets passed down when a step is rebased.
|
|
578
|
-
|
|
579
536
|
const unconfirmedTrs = unconfirmedState.origins;
|
|
580
537
|
const lastTr = unconfirmedTrs[unconfirmedTrs.length - 1];
|
|
581
538
|
let isLastTrConfirmed = false;
|
|
582
|
-
|
|
583
539
|
while (!isLastTrConfirmed) {
|
|
584
540
|
this.sendStepsFromCurrentState();
|
|
585
541
|
await sleep(1000);
|
|
586
542
|
const nextUnconfirmedState = this.getUnconfirmedSteps();
|
|
587
|
-
|
|
588
543
|
if (nextUnconfirmedState && nextUnconfirmedState.steps.length) {
|
|
589
544
|
const nextUnconfirmedTrs = nextUnconfirmedState.origins;
|
|
590
545
|
isLastTrConfirmed = !nextUnconfirmedTrs.some(tr => tr === lastTr);
|
|
591
546
|
} else {
|
|
592
547
|
isLastTrConfirmed = true;
|
|
593
548
|
}
|
|
594
|
-
|
|
595
549
|
if (!isLastTrConfirmed && count++ >= maxAttemptsToSync) {
|
|
596
550
|
if (this.onSyncUpError) {
|
|
597
551
|
const state = this.getState();
|
|
@@ -603,7 +557,6 @@ export class Provider extends Emitter {
|
|
|
603
557
|
version: getVersion(state)
|
|
604
558
|
});
|
|
605
559
|
}
|
|
606
|
-
|
|
607
560
|
const measure = stopMeasure(MEASURE_NAME.COMMIT_UNCONFIRMED_STEPS);
|
|
608
561
|
triggerAnalyticsEvent({
|
|
609
562
|
eventAction: EVENT_ACTION.COMMIT_UNCONFIRMED_STEPS,
|
|
@@ -618,7 +571,6 @@ export class Provider extends Emitter {
|
|
|
618
571
|
throw new Error("Can't sync up with Collab Service");
|
|
619
572
|
}
|
|
620
573
|
}
|
|
621
|
-
|
|
622
574
|
const measure = stopMeasure(MEASURE_NAME.COMMIT_UNCONFIRMED_STEPS);
|
|
623
575
|
triggerAnalyticsEvent({
|
|
624
576
|
eventAction: EVENT_ACTION.COMMIT_UNCONFIRMED_STEPS,
|
|
@@ -631,11 +583,9 @@ export class Provider extends Emitter {
|
|
|
631
583
|
}
|
|
632
584
|
}, this.analyticsClient);
|
|
633
585
|
}
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
586
|
+
const state = this.getState();
|
|
587
|
+
// Convert ProseMirror document in Editor state to ADF document
|
|
637
588
|
let adfDocument;
|
|
638
|
-
|
|
639
589
|
try {
|
|
640
590
|
startMeasure(MEASURE_NAME.CONVERT_PM_TO_ADF);
|
|
641
591
|
adfDocument = new JSONTransformer().encode(state.doc);
|
|
@@ -661,14 +611,12 @@ export class Provider extends Emitter {
|
|
|
661
611
|
}, this.analyticsClient);
|
|
662
612
|
logger(`Error when converting PM document to ADF: `, error);
|
|
663
613
|
}
|
|
664
|
-
|
|
665
614
|
return {
|
|
666
615
|
content: adfDocument,
|
|
667
616
|
title: (_this$metadata$title = this.metadata.title) === null || _this$metadata$title === void 0 ? void 0 : _this$metadata$title.toString(),
|
|
668
617
|
stepVersion: getVersion(state)
|
|
669
618
|
};
|
|
670
619
|
});
|
|
671
|
-
|
|
672
620
|
_defineProperty(this, "onNamespaceStatusChanged", async data => {
|
|
673
621
|
const {
|
|
674
622
|
isLocked,
|
|
@@ -679,13 +627,13 @@ export class Provider extends Emitter {
|
|
|
679
627
|
logger(`Received a namespace status changed event `, {
|
|
680
628
|
data
|
|
681
629
|
});
|
|
682
|
-
|
|
683
630
|
if (isLocked && waitTimeInMs) {
|
|
684
631
|
this.isNamespaceLocked = true;
|
|
685
632
|
logger(`Received a namespace status change event `, {
|
|
686
633
|
isLocked
|
|
687
|
-
});
|
|
634
|
+
});
|
|
688
635
|
|
|
636
|
+
// To protect the collab editing process from locked out due to BE
|
|
689
637
|
setTimeout(() => {
|
|
690
638
|
logger(`The namespace lock has expired`, {
|
|
691
639
|
waitTime: Date.now() - start,
|
|
@@ -695,20 +643,16 @@ export class Provider extends Emitter {
|
|
|
695
643
|
}, waitTimeInMs);
|
|
696
644
|
return;
|
|
697
645
|
}
|
|
698
|
-
|
|
699
646
|
this.isNamespaceLocked = false;
|
|
700
647
|
logger(`The page lock has expired`);
|
|
701
648
|
});
|
|
702
|
-
|
|
703
649
|
this.config = config;
|
|
704
650
|
this.channel = new Channel(config);
|
|
705
651
|
this.isChannelInitialized = false;
|
|
706
|
-
|
|
707
652
|
if (config.analyticsClient) {
|
|
708
653
|
this.analyticsClient = config.analyticsClient;
|
|
709
654
|
}
|
|
710
655
|
}
|
|
711
|
-
|
|
712
656
|
/**
|
|
713
657
|
* Called by collab plugin in editor when it's ready to
|
|
714
658
|
* initialize a collab session.
|
|
@@ -718,22 +662,34 @@ export class Provider extends Emitter {
|
|
|
718
662
|
getState
|
|
719
663
|
});
|
|
720
664
|
}
|
|
721
|
-
|
|
722
665
|
setup({
|
|
723
666
|
getState,
|
|
724
667
|
onSyncUpError
|
|
725
668
|
}) {
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
669
|
+
try {
|
|
670
|
+
this.getState = getState;
|
|
671
|
+
this.onSyncUpError = onSyncUpError || noop;
|
|
672
|
+
this.clientId = getState().plugins.find(p => p.key === 'collab$').spec.config.clientID;
|
|
673
|
+
if (!this.isChannelInitialized) {
|
|
674
|
+
this.initializeChannel();
|
|
675
|
+
this.isChannelInitialized = true;
|
|
676
|
+
}
|
|
677
|
+
} catch (initError) {
|
|
678
|
+
triggerAnalyticsEvent({
|
|
679
|
+
eventAction: EVENT_ACTION.ERROR,
|
|
680
|
+
attributes: {
|
|
681
|
+
attemptedAction: EVENT_ACTION.INIT_PROVIDER,
|
|
682
|
+
documentAri: this.config.documentAri
|
|
683
|
+
},
|
|
684
|
+
nonPrivacySafeAttributes: {
|
|
685
|
+
error: initError
|
|
686
|
+
}
|
|
687
|
+
}, this.analyticsClient);
|
|
688
|
+
throw initError;
|
|
733
689
|
}
|
|
734
|
-
|
|
735
690
|
return this;
|
|
736
691
|
}
|
|
692
|
+
|
|
737
693
|
/**
|
|
738
694
|
* We can use this function to throttle/delay
|
|
739
695
|
* Any send steps operation
|
|
@@ -741,46 +697,40 @@ export class Provider extends Emitter {
|
|
|
741
697
|
* The getState function will return the current EditorState
|
|
742
698
|
* from the EditorView.
|
|
743
699
|
*/
|
|
744
|
-
|
|
745
|
-
|
|
746
700
|
sendStepsFromCurrentState() {
|
|
747
701
|
const state = this.getState && this.getState();
|
|
748
|
-
|
|
749
702
|
if (!state) {
|
|
750
703
|
return;
|
|
751
704
|
}
|
|
752
|
-
|
|
753
705
|
this.send(null, null, state);
|
|
754
706
|
}
|
|
707
|
+
|
|
755
708
|
/**
|
|
756
709
|
* Send steps from transaction to other participants
|
|
710
|
+
* It needs the superfluous arguments because we keep the interface of the send API the same as the Synchrony plugin
|
|
757
711
|
*/
|
|
758
|
-
|
|
759
|
-
|
|
760
712
|
send(_tr, _oldState, newState) {
|
|
761
713
|
const sendable = sendableSteps(newState);
|
|
762
|
-
const version = getVersion(newState);
|
|
714
|
+
const version = getVersion(newState);
|
|
763
715
|
|
|
716
|
+
// Don't send any steps before we're ready.
|
|
764
717
|
if (!sendable) {
|
|
765
718
|
return;
|
|
766
719
|
}
|
|
767
|
-
|
|
768
720
|
if (this.isNamespaceLocked) {
|
|
769
721
|
logger('The document is temporary locked');
|
|
770
722
|
return;
|
|
771
723
|
}
|
|
772
|
-
|
|
773
724
|
const {
|
|
774
725
|
steps
|
|
775
726
|
} = sendable;
|
|
776
|
-
|
|
777
727
|
if (!steps || !steps.length) {
|
|
778
728
|
return;
|
|
779
|
-
}
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
// Avoid reference issues using a
|
|
780
732
|
// method outside of the provider
|
|
781
733
|
// scope
|
|
782
|
-
|
|
783
|
-
|
|
784
734
|
throttledCommitStep({
|
|
785
735
|
channel: this.channel,
|
|
786
736
|
userId: this.userId,
|
|
@@ -792,44 +742,39 @@ export class Provider extends Emitter {
|
|
|
792
742
|
onStepsAdded: this.onStepsAdded.bind(this),
|
|
793
743
|
onErrorHandled: this.onErrorHandled.bind(this)
|
|
794
744
|
});
|
|
795
|
-
}
|
|
745
|
+
}
|
|
796
746
|
|
|
747
|
+
// Triggered when page recovery has emitted an 'init' event on a page client is currently connected to.
|
|
797
748
|
|
|
798
749
|
queueSteps(data) {
|
|
799
750
|
logger(`Queueing data for version "${data.version}".`);
|
|
800
751
|
const orderedQueue = [...this.queue, data].sort((a, b) => a.version > b.version ? 1 : -1);
|
|
801
752
|
this.queue = orderedQueue;
|
|
802
753
|
}
|
|
803
|
-
|
|
804
754
|
processQueue() {
|
|
805
755
|
if (this.pauseQueue) {
|
|
806
756
|
logger(`Queue is paused. Aborting.`);
|
|
807
757
|
return;
|
|
808
758
|
}
|
|
809
|
-
|
|
810
759
|
logger(`Looking for processable data.`);
|
|
811
|
-
|
|
812
760
|
if (this.queue.length > 0) {
|
|
813
761
|
const firstItem = this.queue.shift();
|
|
814
762
|
const currentVersion = this.getCurrentPmVersion();
|
|
815
763
|
const expectedVersion = currentVersion + firstItem.steps.length;
|
|
816
|
-
|
|
817
764
|
if (firstItem.version === expectedVersion) {
|
|
818
765
|
logger(`Applying data from queue!`);
|
|
819
|
-
this.processSteps(firstItem);
|
|
820
|
-
|
|
766
|
+
this.processSteps(firstItem);
|
|
767
|
+
// recur
|
|
821
768
|
this.processQueue();
|
|
822
769
|
}
|
|
823
770
|
}
|
|
824
771
|
}
|
|
825
|
-
|
|
826
772
|
processSteps(data, disableAnalytics = false) {
|
|
827
773
|
const {
|
|
828
774
|
version,
|
|
829
775
|
steps
|
|
830
776
|
} = data;
|
|
831
777
|
logger(`Processing data. Version "${version}".`);
|
|
832
|
-
|
|
833
778
|
if (steps !== null && steps !== void 0 && steps.length) {
|
|
834
779
|
const clientIds = steps.map(({
|
|
835
780
|
clientId
|
|
@@ -838,10 +783,10 @@ export class Provider extends Emitter {
|
|
|
838
783
|
json: steps,
|
|
839
784
|
version,
|
|
840
785
|
userIds: clientIds
|
|
841
|
-
});
|
|
842
|
-
|
|
843
|
-
this.stepRejectCounter = 0;
|
|
844
|
-
|
|
786
|
+
});
|
|
787
|
+
// If steps can apply to local editor successfully, no need to accumulate the error counter.
|
|
788
|
+
this.stepRejectCounter = 0;
|
|
789
|
+
// TODO: Remove this analytics logic after we've validated the ack call-backs
|
|
845
790
|
if (!disableAnalytics && clientIds.indexOf(this.clientId) >= 0) {
|
|
846
791
|
let analyticStepEvent = {
|
|
847
792
|
eventAction: EVENT_ACTION.ADD_STEPS,
|
|
@@ -854,24 +799,22 @@ export class Provider extends Emitter {
|
|
|
854
799
|
analyticStepEvent.attributes.stepType = countBy(steps, step => step.stepType);
|
|
855
800
|
triggerAnalyticsEvent(analyticStepEvent, this.analyticsClient);
|
|
856
801
|
}
|
|
802
|
+
this.emitTelepointersFromSteps(steps);
|
|
857
803
|
|
|
858
|
-
|
|
859
|
-
|
|
804
|
+
// Resend local steps if none of the received steps originated with us!
|
|
860
805
|
if (clientIds.indexOf(this.clientId) === -1) {
|
|
861
806
|
setTimeout(() => this.sendStepsFromCurrentState(), 100);
|
|
862
807
|
}
|
|
863
808
|
}
|
|
864
809
|
}
|
|
810
|
+
|
|
865
811
|
/**
|
|
866
812
|
* Send messages, such as telepointers, to other participants.
|
|
867
813
|
*/
|
|
868
|
-
|
|
869
|
-
|
|
870
814
|
sendMessage(data) {
|
|
871
815
|
if (!data) {
|
|
872
816
|
return;
|
|
873
817
|
}
|
|
874
|
-
|
|
875
818
|
const {
|
|
876
819
|
type,
|
|
877
820
|
...rest
|
|
@@ -881,7 +824,6 @@ export class Provider extends Emitter {
|
|
|
881
824
|
sessionId,
|
|
882
825
|
clientId
|
|
883
826
|
} = this;
|
|
884
|
-
|
|
885
827
|
switch (type) {
|
|
886
828
|
case 'telepointer':
|
|
887
829
|
const telepointerExperience = new UFOExperience('collab-provider.telepointer', {
|
|
@@ -925,11 +867,9 @@ export class Provider extends Emitter {
|
|
|
925
867
|
break;
|
|
926
868
|
}
|
|
927
869
|
}
|
|
928
|
-
|
|
929
870
|
emitTelepointersFromSteps(steps) {
|
|
930
871
|
steps.forEach(step => {
|
|
931
872
|
const [participant] = Array.from(this.participants.values()).filter(p => p.clientId === step.clientId);
|
|
932
|
-
|
|
933
873
|
if (participant) {
|
|
934
874
|
const {
|
|
935
875
|
stepType,
|
|
@@ -940,7 +880,6 @@ export class Provider extends Emitter {
|
|
|
940
880
|
}
|
|
941
881
|
} = step;
|
|
942
882
|
const [node] = slice.content;
|
|
943
|
-
|
|
944
883
|
if (stepType === 'replace' && to === from && slice.content.length === 1 && node.type === 'text' && node.text.length === 1) {
|
|
945
884
|
this.emit('telepointer', {
|
|
946
885
|
sessionId: participant.sessionId,
|
|
@@ -955,44 +894,36 @@ export class Provider extends Emitter {
|
|
|
955
894
|
}
|
|
956
895
|
});
|
|
957
896
|
}
|
|
958
|
-
|
|
959
897
|
destroy() {
|
|
960
898
|
return this.disconnect();
|
|
961
899
|
}
|
|
962
|
-
|
|
963
900
|
disconnect() {
|
|
964
901
|
return this.unsubscribeAll();
|
|
965
902
|
}
|
|
966
|
-
|
|
967
903
|
setTitle(title, broadcast) {
|
|
968
904
|
if (broadcast) {
|
|
969
905
|
this.channel.sendMetadata({
|
|
970
906
|
title
|
|
971
907
|
});
|
|
972
908
|
}
|
|
973
|
-
|
|
974
909
|
Object.assign(this.metadata, {
|
|
975
910
|
title
|
|
976
911
|
});
|
|
977
912
|
}
|
|
978
|
-
|
|
979
913
|
setEditorWidth(editorWidth, broadcast) {
|
|
980
914
|
if (broadcast) {
|
|
981
915
|
this.channel.sendMetadata({
|
|
982
916
|
editorWidth
|
|
983
917
|
});
|
|
984
918
|
}
|
|
985
|
-
|
|
986
919
|
Object.assign(this.metadata, {
|
|
987
920
|
editorWidth
|
|
988
921
|
});
|
|
989
922
|
}
|
|
990
|
-
|
|
991
923
|
setMetadata(metadata) {
|
|
992
924
|
this.channel.sendMetadata(metadata);
|
|
993
925
|
Object.assign(this.metadata, metadata);
|
|
994
926
|
}
|
|
995
|
-
|
|
996
927
|
/**
|
|
997
928
|
* Unsubscribe from all events emitted by this provider.
|
|
998
929
|
*/
|
|
@@ -1001,9 +932,8 @@ export class Provider extends Emitter {
|
|
|
1001
932
|
this.channel.disconnect();
|
|
1002
933
|
return this;
|
|
1003
934
|
}
|
|
935
|
+
|
|
1004
936
|
/**
|
|
1005
937
|
* ESS-2916 namespace status event- lock/unlock
|
|
1006
938
|
*/
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
939
|
}
|