@atlaskit/collab-provider 7.0.0 → 7.1.2

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 (37) hide show
  1. package/.vscode/settings.json +3 -0
  2. package/CHANGELOG.md +40 -0
  3. package/disconnected-reason-mapper/package.json +7 -0
  4. package/dist/cjs/analytics/index.js +1 -1
  5. package/dist/cjs/analytics/performance.js +1 -1
  6. package/dist/cjs/channel.js +56 -19
  7. package/dist/cjs/disconnected-reason-mapper.js +31 -0
  8. package/dist/cjs/error-code-mapper.js +43 -2
  9. package/dist/cjs/helpers/const.js +1 -1
  10. package/dist/cjs/helpers/utils.js +9 -14
  11. package/dist/cjs/provider/catchup.js +147 -0
  12. package/dist/cjs/{provider.js → provider/index.js} +176 -265
  13. package/dist/cjs/socket-io-provider.js +1 -1
  14. package/dist/cjs/version.json +1 -1
  15. package/dist/es2019/channel.js +34 -7
  16. package/dist/es2019/disconnected-reason-mapper.js +23 -0
  17. package/dist/es2019/error-code-mapper.js +38 -0
  18. package/dist/es2019/helpers/utils.js +9 -14
  19. package/dist/es2019/provider/catchup.js +91 -0
  20. package/dist/es2019/{provider.js → provider/index.js} +126 -195
  21. package/dist/es2019/version.json +1 -1
  22. package/dist/esm/channel.js +54 -19
  23. package/dist/esm/disconnected-reason-mapper.js +23 -0
  24. package/dist/esm/error-code-mapper.js +38 -0
  25. package/dist/esm/helpers/utils.js +8 -13
  26. package/dist/esm/provider/catchup.js +131 -0
  27. package/dist/esm/{provider.js → provider/index.js} +180 -267
  28. package/dist/esm/version.json +1 -1
  29. package/dist/types/channel.d.ts +18 -26
  30. package/dist/types/disconnected-reason-mapper.d.ts +15 -0
  31. package/dist/types/error-code-mapper.d.ts +3 -0
  32. package/dist/types/helpers/utils.d.ts +2 -2
  33. package/dist/types/index.d.ts +1 -1
  34. package/dist/types/provider/catchup.d.ts +24 -0
  35. package/dist/types/{provider.d.ts → provider/index.d.ts} +22 -11
  36. package/package.json +11 -8
  37. package/provider/package.json +0 -7
@@ -5,8 +5,8 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
5
5
  Object.defineProperty(exports, "__esModule", {
6
6
  value: true
7
7
  });
8
- exports.createSocketIOSocket = createSocketIOSocket;
9
8
  exports.createSocketIOCollabProvider = createSocketIOCollabProvider;
9
+ exports.createSocketIOSocket = createSocketIOSocket;
10
10
 
11
11
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
12
12
 
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@atlaskit/collab-provider",
3
- "version": "7.0.0",
3
+ "version": "7.1.2",
4
4
  "sideEffects": false
5
5
  }
@@ -3,6 +3,8 @@ import { utils } from '@atlaskit/util-service-support';
3
3
  import { Emitter } from './emitter';
4
4
  import { ErrorCodeMapper } from './error-code-mapper';
5
5
  import { createLogger } from './helpers/utils';
6
+ import { startMeasure, stopMeasure } from './analytics/performance';
7
+ import { triggerAnalyticsForCatchupSuccessfulWithLatency } from './analytics';
6
8
  const logger = createLogger('Channel', 'green');
7
9
  export class Channel extends Emitter {
8
10
  constructor(config) {
@@ -55,6 +57,10 @@ export class Channel extends Emitter {
55
57
  });
56
58
 
57
59
  this.config = config;
60
+
61
+ if (config.analyticsClient) {
62
+ this.analyticsClient = config.analyticsClient;
63
+ }
58
64
  } // read-only getters used for tests
59
65
 
60
66
 
@@ -110,8 +116,11 @@ export class Channel extends Emitter {
110
116
  this.socket.on('participant:telepointer', payload => {
111
117
  this.emit('participant:telepointer', payload.data);
112
118
  });
113
- this.socket.on('participant:joined', data => {
114
- this.emit('participant:joined', data);
119
+ this.socket.on('presence:joined', data => {
120
+ this.emit('presence:joined', data);
121
+ });
122
+ this.socket.on('presence', data => {
123
+ this.emit('presence', data);
115
124
  });
116
125
  this.socket.on('participant:left', data => {
117
126
  this.emit('participant:left', data);
@@ -129,11 +138,8 @@ export class Channel extends Emitter {
129
138
  ...data
130
139
  });
131
140
  });
132
- this.socket.on('title:changed', payload => {
133
- this.emit('title:changed', payload.data);
134
- });
135
- this.socket.on('width:changed', payload => {
136
- this.emit('width:changed', payload.data);
141
+ this.socket.on('metadata:changed', payload => {
142
+ this.emit('metadata:changed', payload);
137
143
  });
138
144
  this.socket.on('disconnect', async reason => {
139
145
  this.connected = false;
@@ -173,6 +179,7 @@ export class Channel extends Emitter {
173
179
 
174
180
  async fetchCatchup(fromVersion) {
175
181
  try {
182
+ startMeasure('callingCatchupApi');
176
183
  const {
177
184
  doc,
178
185
  version,
@@ -208,6 +215,10 @@ export class Channel extends Emitter {
208
215
  };
209
216
  this.emit('error', errorCatchup);
210
217
  return {};
218
+ } finally {
219
+ stopMeasure('callingCatchupApi', (duration, _) => {
220
+ triggerAnalyticsForCatchupSuccessfulWithLatency(this.analyticsClient, duration);
221
+ });
211
222
  }
212
223
  }
213
224
  /**
@@ -226,6 +237,22 @@ export class Channel extends Emitter {
226
237
  });
227
238
  }
228
239
 
240
+ sendMetadata(metadata) {
241
+ if (!this.connected || !this.socket) {
242
+ return;
243
+ }
244
+
245
+ this.socket.emit('metadata', metadata);
246
+ }
247
+
248
+ sendPresenceJoined() {
249
+ if (!this.connected || !this.socket) {
250
+ return;
251
+ }
252
+
253
+ this.socket.emit('presence:joined');
254
+ }
255
+
229
256
  disconnect() {
230
257
  this.unsubscribeAll();
231
258
 
@@ -0,0 +1,23 @@
1
+ // See https://socket.io/docs/v3/client-socket-instance#disconnect for emitted reasons
2
+ export const socketIOReasons = {
3
+ IO_CLIENT_DISCONNECT: 'io client disconnect',
4
+ // The socket was manually disconnected using socket.disconnect()
5
+ IO_SERVER_DISCONNECT: 'io server disconnect',
6
+ // The server has forcefully disconnected the socket with socket.disconnect()
7
+ TRANSPORT_CLOSED: 'transport close',
8
+ // The server did not send a PING within the pingInterval + pingTimeout range
9
+ TRANSPORT_ERROR: 'transport error',
10
+ // The connection was closed (example: the user has lost connection, or the network was changed from WiFi to 4G)
11
+ PING_TIMEOUT: 'ping timeout' // The connection has encountered an error (example: the server was killed during a HTTP long-polling cycle)
12
+
13
+ };
14
+ export let DisconnectReason;
15
+
16
+ (function (DisconnectReason) {
17
+ DisconnectReason["CLIENT_DISCONNECT"] = "CLIENT_DISCONNECT";
18
+ DisconnectReason["SERVER_DISCONNECT"] = "SERVER_DISCONNECT";
19
+ DisconnectReason["SOCKET_CLOSED"] = "SOCKET_CLOSED";
20
+ DisconnectReason["SOCKET_ERROR"] = "SOCKET_ERROR";
21
+ DisconnectReason["SOCKET_TIMEOUT"] = "SOCKET_TIMEOUT";
22
+ DisconnectReason["UNKNOWN_DISCONNECT"] = "UNKNOWN_DISCONNECT";
23
+ })(DisconnectReason || (DisconnectReason = {}));
@@ -27,4 +27,42 @@ export const ErrorCodeMapper = {
27
27
  code: 'INTERNAL_SERVICE_ERROR',
28
28
  message: 'Collab service has return internal server error'
29
29
  }
30
+ };
31
+ export const errorCodeMapper = error => {
32
+ if (error.data) {
33
+ switch (error.data.code) {
34
+ case 'INSUFFICIENT_EDITING_PERMISSION':
35
+ return {
36
+ status: 403,
37
+ code: ErrorCodeMapper.noPermissionError.code,
38
+ message: ErrorCodeMapper.noPermissionError.message
39
+ };
40
+
41
+ case 'DOCUMENT_NOT_FOUND':
42
+ return {
43
+ status: 404,
44
+ code: ErrorCodeMapper.documentNotFound.code,
45
+ message: ErrorCodeMapper.documentNotFound.message
46
+ };
47
+
48
+ case 'FAILED_ON_S3':
49
+ case 'DYNAMO_ERROR':
50
+ return {
51
+ status: 500,
52
+ code: ErrorCodeMapper.failToSave.code,
53
+ message: ErrorCodeMapper.failToSave.message
54
+ };
55
+
56
+ case 'CATCHUP_FAILED':
57
+ case 'GET_QUERY_TIME_OUT':
58
+ return {
59
+ status: 500,
60
+ code: ErrorCodeMapper.internalError.code,
61
+ message: ErrorCodeMapper.internalError.message
62
+ };
63
+
64
+ default:
65
+ break;
66
+ }
67
+ }
30
68
  };
@@ -1,23 +1,18 @@
1
1
  export const createLogger = (prefix, color = 'blue') => (msg, data = null) => {
2
2
  if (window.COLLAB_PROVIDER_LOGGER) {
3
3
  // eslint-disable-next-line no-console
4
- console.log(`%cCollab-${prefix}: ${msg}`, `color: ${color}; font-weight: bold`);
5
-
6
- if (data) {
7
- // eslint-disable-next-line no-console
8
- console.log(data);
9
- }
4
+ console.log(`%cCollab-${prefix}: ${msg}`, `color: ${color}; font-weight: bold`, data);
10
5
  }
11
6
  };
7
+ const logger = createLogger('Helper:util', 'black');
12
8
  export const getParticipant = userId => {
13
- // eslint-disable-next-line no-bitwise
14
- const name = 'Demo User';
15
- return Promise.resolve({
16
- userId,
17
- name,
18
- avatar: `https://api.adorable.io/avatars/80/${name.replace(/\s/g, '')}.png`,
19
- email: `${name.replace(/\s/g, '').toLocaleLowerCase()}@atlassian.com`
20
- });
9
+ logger('getParticipant: ', userId);
10
+ return {
11
+ userId: userId,
12
+ name: userId,
13
+ avatar: '',
14
+ email: `${userId.replace(/\s/g, '').toLocaleLowerCase()}@atlassian.com`
15
+ };
21
16
  };
22
17
  export function sleep(ms) {
23
18
  return new Promise(resolve => {
@@ -0,0 +1,91 @@
1
+ import { createLogger } from '../helpers/utils';
2
+ import { StepMap, Mapping } from 'prosemirror-transform';
3
+ const logger = createLogger('Catchup', 'red');
4
+ /**
5
+ * Rebase the steps based on the mapping pipeline.
6
+ * Some steps could be lost, if they are no longer
7
+ * invalid after rebased.
8
+ */
9
+
10
+ export function rebaseSteps(steps, mapping) {
11
+ const newSteps = [];
12
+
13
+ for (const step of steps) {
14
+ const newStep = step.map(mapping); // newStep could be null(means invalid after rebase) when can't rebase.
15
+
16
+ if (newStep) {
17
+ newSteps.push(newStep);
18
+ }
19
+ }
20
+
21
+ return newSteps;
22
+ }
23
+ export const catchup = async opt => {
24
+ const {
25
+ doc,
26
+ stepMaps: serverStepMaps,
27
+ version: serverVersion,
28
+ metadata
29
+ } = await opt.fetchCatchup(opt.getCurrentPmVersion());
30
+
31
+ if (doc) {
32
+ const currentPmVersion = opt.getCurrentPmVersion();
33
+
34
+ if (typeof serverVersion === 'undefined') {
35
+ logger(`Could not determine server version`);
36
+ } else if (serverVersion <= currentPmVersion) {
37
+ logger(`Catchup steps we already have. Ignoring.`);
38
+ } else {
39
+ // Please, do not use those steps inside of async
40
+ // method. That will lead to outdated steps
41
+ const {
42
+ steps: unconfirmedSteps
43
+ } = opt.getUnconfirmedSteps() || {
44
+ steps: []
45
+ };
46
+ logger(`Too far behind[current: v${currentPmVersion}, server: v${serverVersion}. ${serverStepMaps.length} steps need to catchup]`);
47
+ /**
48
+ * Remove steps from queue where the version is older than
49
+ * the version we received from service. Keep steps that might be
50
+ * newer.
51
+ */
52
+
53
+ opt.fitlerQueue(data => data.version > serverVersion); // We are too far behind - replace the entire document
54
+
55
+ logger(`Replacing document: ${doc}`);
56
+ logger(`getting metadata: ${metadata}`); // Replace local document and version number
57
+
58
+ opt.updateDocumentWithMetadata({
59
+ doc: JSON.parse(doc),
60
+ version: serverVersion,
61
+ metadata,
62
+ reserveCursor: true
63
+ }); // After replacing the whole document in the editor, we need to reapply the unconfirmed
64
+ // steps back into the editor, so we don't lose any data. But before that, we need to rebase
65
+ // those steps since their position could be changed after replacing.
66
+ // https://prosemirror.net/docs/guide/#transform.rebasing
67
+
68
+ if (unconfirmedSteps.length) {
69
+ // Create StepMap from StepMap JSON
70
+ // eslint-disable-next-line no-unused-vars
71
+ const stepMaps = serverStepMaps.map(({
72
+ ranges,
73
+ inverted
74
+ }) => {
75
+ // Due to @types/prosemirror-transform mismatch with the actual
76
+ // constructor, hack to set the `inverted`.
77
+ const stepMap = new StepMap(ranges);
78
+ stepMap.inverted = inverted;
79
+ return stepMap;
80
+ }); // create Mappng used for Step.map
81
+
82
+ const mapping = new Mapping(stepMaps);
83
+ logger(`${unconfirmedSteps.length} unconfirmed steps before rebased: ${JSON.stringify(unconfirmedSteps)}`);
84
+ const newUnconfirmedSteps = rebaseSteps(unconfirmedSteps, mapping);
85
+ logger(`Re-aply ${newUnconfirmedSteps.length} mapped unconfirmed steps: ${JSON.stringify(newUnconfirmedSteps)}`); // Re-aply local steps
86
+
87
+ opt.applyLocalsteps(newUnconfirmedSteps);
88
+ }
89
+ }
90
+ }
91
+ };