@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.
- package/.vscode/settings.json +3 -0
- package/CHANGELOG.md +40 -0
- package/disconnected-reason-mapper/package.json +7 -0
- package/dist/cjs/analytics/index.js +1 -1
- package/dist/cjs/analytics/performance.js +1 -1
- package/dist/cjs/channel.js +56 -19
- package/dist/cjs/disconnected-reason-mapper.js +31 -0
- package/dist/cjs/error-code-mapper.js +43 -2
- package/dist/cjs/helpers/const.js +1 -1
- package/dist/cjs/helpers/utils.js +9 -14
- package/dist/cjs/provider/catchup.js +147 -0
- package/dist/cjs/{provider.js → provider/index.js} +176 -265
- package/dist/cjs/socket-io-provider.js +1 -1
- package/dist/cjs/version.json +1 -1
- package/dist/es2019/channel.js +34 -7
- package/dist/es2019/disconnected-reason-mapper.js +23 -0
- package/dist/es2019/error-code-mapper.js +38 -0
- package/dist/es2019/helpers/utils.js +9 -14
- package/dist/es2019/provider/catchup.js +91 -0
- package/dist/es2019/{provider.js → provider/index.js} +126 -195
- package/dist/es2019/version.json +1 -1
- package/dist/esm/channel.js +54 -19
- package/dist/esm/disconnected-reason-mapper.js +23 -0
- package/dist/esm/error-code-mapper.js +38 -0
- package/dist/esm/helpers/utils.js +8 -13
- package/dist/esm/provider/catchup.js +131 -0
- package/dist/esm/{provider.js → provider/index.js} +180 -267
- package/dist/esm/version.json +1 -1
- package/dist/types/channel.d.ts +18 -26
- package/dist/types/disconnected-reason-mapper.d.ts +15 -0
- package/dist/types/error-code-mapper.d.ts +3 -0
- package/dist/types/helpers/utils.d.ts +2 -2
- package/dist/types/index.d.ts +1 -1
- package/dist/types/provider/catchup.d.ts +24 -0
- package/dist/types/{provider.d.ts → provider/index.d.ts} +22 -11
- package/package.json +11 -8
- 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
|
|
package/dist/cjs/version.json
CHANGED
package/dist/es2019/channel.js
CHANGED
|
@@ -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('
|
|
114
|
-
this.emit('
|
|
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('
|
|
133
|
-
this.emit('
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
userId,
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
+
};
|