@atlaskit/collab-provider 8.1.0 → 8.3.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 +48 -0
- package/dist/cjs/analytics/index.js +68 -20
- package/dist/cjs/analytics/performance.js +45 -49
- package/dist/cjs/analytics/ufo.js +33 -0
- package/dist/cjs/channel.js +138 -154
- package/dist/cjs/connectivity/network.js +53 -0
- package/dist/cjs/connectivity/reconnect-helper.js +48 -0
- package/dist/cjs/connectivity/singleton.js +15 -0
- package/dist/cjs/disconnected-reason-mapper.js +19 -4
- package/dist/cjs/emitter.js +3 -11
- package/dist/cjs/error-code-mapper.js +14 -12
- package/dist/cjs/feature-flags/__test__/index.unit.js +0 -1
- package/dist/cjs/feature-flags/index.js +5 -15
- package/dist/cjs/feature-flags/types.js +2 -0
- package/dist/cjs/helpers/const.js +7 -18
- package/dist/cjs/helpers/utils.js +0 -12
- package/dist/cjs/index.js +0 -1
- package/dist/cjs/provider/catchup.js +35 -43
- package/dist/cjs/provider/commit-step.js +66 -0
- package/dist/cjs/provider/index.js +533 -742
- package/dist/cjs/provider/telepointers.js +78 -0
- 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 +59 -11
- package/dist/es2019/analytics/performance.js +46 -46
- package/dist/es2019/analytics/ufo.js +22 -0
- package/dist/es2019/channel.js +128 -107
- package/dist/es2019/connectivity/network.js +34 -0
- package/dist/es2019/connectivity/reconnect-helper.js +29 -0
- package/dist/es2019/connectivity/singleton.js +7 -0
- package/dist/es2019/disconnected-reason-mapper.js +18 -3
- package/dist/es2019/emitter.js +3 -8
- package/dist/es2019/error-code-mapper.js +14 -7
- package/dist/es2019/feature-flags/index.js +2 -5
- package/dist/es2019/feature-flags/types.js +2 -0
- package/dist/es2019/helpers/const.js +6 -15
- package/dist/es2019/helpers/utils.js +0 -2
- package/dist/es2019/provider/catchup.js +32 -23
- package/dist/es2019/provider/commit-step.js +53 -0
- package/dist/es2019/provider/index.js +446 -616
- package/dist/es2019/provider/telepointers.js +65 -0
- 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 +69 -15
- package/dist/esm/analytics/performance.js +46 -46
- package/dist/esm/analytics/ufo.js +25 -0
- package/dist/esm/channel.js +139 -148
- package/dist/esm/connectivity/network.js +45 -0
- package/dist/esm/connectivity/reconnect-helper.js +42 -0
- package/dist/esm/connectivity/singleton.js +7 -0
- package/dist/esm/disconnected-reason-mapper.js +18 -3
- package/dist/esm/emitter.js +3 -6
- package/dist/esm/error-code-mapper.js +14 -7
- package/dist/esm/feature-flags/index.js +5 -10
- package/dist/esm/feature-flags/types.js +2 -0
- package/dist/esm/helpers/const.js +6 -15
- package/dist/esm/helpers/utils.js +0 -3
- package/dist/esm/provider/catchup.js +35 -36
- package/dist/esm/provider/commit-step.js +58 -0
- package/dist/esm/provider/index.js +535 -745
- package/dist/esm/provider/telepointers.js +69 -0
- 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/analytics/index.d.ts +8 -2
- package/dist/types/analytics/performance.d.ts +5 -5
- package/dist/types/analytics/ufo.d.ts +3 -0
- package/dist/types/channel.d.ts +12 -5
- package/dist/types/connectivity/network.d.ts +17 -0
- package/dist/types/connectivity/reconnect-helper.d.ts +8 -0
- package/dist/types/connectivity/singleton.d.ts +3 -0
- package/dist/types/disconnected-reason-mapper.d.ts +1 -0
- package/dist/types/error-code-mapper.d.ts +4 -0
- package/dist/types/helpers/const.d.ts +116 -19
- package/dist/types/provider/commit-step.d.ts +14 -0
- package/dist/types/provider/index.d.ts +9 -5
- package/dist/types/provider/telepointers.d.ts +5 -0
- package/dist/types/socket-io-provider.d.ts +2 -1
- package/dist/types/types.d.ts +30 -11
- package/package.json +7 -6
- package/report.api.md +35 -0
- package/.vscode/settings.json +0 -3
package/dist/es2019/channel.js
CHANGED
|
@@ -1,79 +1,88 @@
|
|
|
1
1
|
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
2
|
-
import { utils } from '@atlaskit/util-service-support';
|
|
3
|
-
|
|
2
|
+
import { utils } from '@atlaskit/util-service-support';
|
|
4
3
|
import { Emitter } from './emitter';
|
|
5
4
|
import { ErrorCodeMapper } from './error-code-mapper';
|
|
6
5
|
import { createLogger, getProduct, getSubProduct } from './helpers/utils';
|
|
7
6
|
import { MEASURE_NAME, startMeasure, stopMeasure } from './analytics/performance';
|
|
8
|
-
import { triggerAnalyticsEvent } from './analytics';
|
|
9
7
|
import { EVENT_ACTION, EVENT_STATUS } from './helpers/const';
|
|
10
|
-
import
|
|
8
|
+
import ReconnectHelper from './connectivity/reconnect-helper';
|
|
9
|
+
import { createDocInitExp } from './analytics/ufo';
|
|
10
|
+
import Network from './connectivity/network';
|
|
11
11
|
const logger = createLogger('Channel', 'green');
|
|
12
12
|
export class Channel extends Emitter {
|
|
13
|
-
constructor(config) {
|
|
13
|
+
constructor(config, analyticsHelper) {
|
|
14
14
|
super();
|
|
15
|
-
|
|
16
15
|
_defineProperty(this, "connected", false);
|
|
17
|
-
|
|
18
16
|
_defineProperty(this, "socket", null);
|
|
19
|
-
|
|
17
|
+
_defineProperty(this, "reconnectHelper", null);
|
|
20
18
|
_defineProperty(this, "initialized", false);
|
|
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
|
-
|
|
19
|
+
_defineProperty(this, "network", null);
|
|
34
20
|
_defineProperty(this, "getInitialized", () => this.initialized);
|
|
35
|
-
|
|
36
21
|
_defineProperty(this, "getConnected", () => this.connected);
|
|
37
|
-
|
|
38
22
|
_defineProperty(this, "getSocket", () => this.socket);
|
|
39
|
-
|
|
23
|
+
_defineProperty(this, "onConnectError", error => {
|
|
24
|
+
var _this$analyticsHelper, _this$analyticsHelper2;
|
|
25
|
+
const measure = stopMeasure(MEASURE_NAME.SOCKET_CONNECT, this.analyticsHelper);
|
|
26
|
+
(_this$analyticsHelper = this.analyticsHelper) === null || _this$analyticsHelper === void 0 ? void 0 : _this$analyticsHelper.sendActionEvent(EVENT_ACTION.CONNECTION, EVENT_STATUS.FAILURE, {
|
|
27
|
+
latency: measure === null || measure === void 0 ? void 0 : measure.duration
|
|
28
|
+
});
|
|
29
|
+
(_this$analyticsHelper2 = this.analyticsHelper) === null || _this$analyticsHelper2 === void 0 ? void 0 : _this$analyticsHelper2.sendErrorEvent(error, 'Error while establishing connection');
|
|
30
|
+
// If error received with `data`, it means the connection is rejected
|
|
31
|
+
// by the server on purpose for example no permission, so no need to
|
|
32
|
+
// keep the underneath connection, need to close. But some error like
|
|
33
|
+
// `xhr polling error` needs to retry.
|
|
34
|
+
if (!!error.data) {
|
|
35
|
+
var _this$socket;
|
|
36
|
+
(_this$socket = this.socket) === null || _this$socket === void 0 ? void 0 : _this$socket.close();
|
|
37
|
+
}
|
|
38
|
+
this.emit('error', {
|
|
39
|
+
message: error.message,
|
|
40
|
+
data: error.data
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
_defineProperty(this, "onReconnectError", error => {
|
|
44
|
+
var _this$reconnectHelper, _this$reconnectHelper2;
|
|
45
|
+
(_this$reconnectHelper = this.reconnectHelper) === null || _this$reconnectHelper === void 0 ? void 0 : _this$reconnectHelper.countReconnectError();
|
|
46
|
+
if ((_this$reconnectHelper2 = this.reconnectHelper) !== null && _this$reconnectHelper2 !== void 0 && _this$reconnectHelper2.isLikelyNetworkIssue()) {
|
|
47
|
+
var _this$analyticsHelper3;
|
|
48
|
+
(_this$analyticsHelper3 = this.analyticsHelper) === null || _this$analyticsHelper3 === void 0 ? void 0 : _this$analyticsHelper3.sendErrorEvent(error, 'Likely network issue while reconnecting the channel');
|
|
49
|
+
this.emit('error', {
|
|
50
|
+
message: 'Reconnection failed 8 times when browser was offline, likely there was a network issue.',
|
|
51
|
+
data: {
|
|
52
|
+
status: 400,
|
|
53
|
+
code: 'RECONNECTION_NETWORK_ISSUE'
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
});
|
|
40
58
|
_defineProperty(this, "onConnect", () => {
|
|
59
|
+
var _this$analyticsHelper4;
|
|
41
60
|
this.connected = true;
|
|
42
61
|
logger('Connected.', this.socket.id);
|
|
43
|
-
const measure = stopMeasure(MEASURE_NAME.SOCKET_CONNECT);
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
eventStatus: EVENT_STATUS.SUCCESS,
|
|
48
|
-
latency: measure === null || measure === void 0 ? void 0 : measure.duration,
|
|
49
|
-
documentAri: this.config.documentAri
|
|
50
|
-
}
|
|
51
|
-
}, this.analyticsClient);
|
|
62
|
+
const measure = stopMeasure(MEASURE_NAME.SOCKET_CONNECT, this.analyticsHelper);
|
|
63
|
+
(_this$analyticsHelper4 = this.analyticsHelper) === null || _this$analyticsHelper4 === void 0 ? void 0 : _this$analyticsHelper4.sendActionEvent(EVENT_ACTION.CONNECTION, EVENT_STATUS.SUCCESS, {
|
|
64
|
+
latency: measure === null || measure === void 0 ? void 0 : measure.duration
|
|
65
|
+
});
|
|
52
66
|
this.emit('connected', {
|
|
53
67
|
sid: this.socket.id,
|
|
54
68
|
initialized: this.initialized
|
|
55
69
|
});
|
|
56
70
|
});
|
|
57
|
-
|
|
58
71
|
_defineProperty(this, "onReceiveData", data => {
|
|
59
72
|
logger('Received data', data);
|
|
60
73
|
logger('Session ID is', this.socket.id);
|
|
61
|
-
|
|
62
74
|
if (data.type === 'initial') {
|
|
63
75
|
if (!this.initialized) {
|
|
64
|
-
|
|
65
|
-
this.
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
ttlEnabled: data === null || data === void 0 ? void 0 : data.ttlEnabled
|
|
75
|
-
}
|
|
76
|
-
}, this.analyticsClient);
|
|
76
|
+
var _this$initExperience, _this$analyticsHelper5;
|
|
77
|
+
const measure = stopMeasure(MEASURE_NAME.DOCUMENT_INIT, this.analyticsHelper);
|
|
78
|
+
(_this$initExperience = this.initExperience) === null || _this$initExperience === void 0 ? void 0 : _this$initExperience.success();
|
|
79
|
+
(_this$analyticsHelper5 = this.analyticsHelper) === null || _this$analyticsHelper5 === void 0 ? void 0 : _this$analyticsHelper5.sendActionEvent(EVENT_ACTION.DOCUMENT_INIT,
|
|
80
|
+
// TODO: detect when document init fails and fire corresponding event for it
|
|
81
|
+
EVENT_STATUS.SUCCESS, {
|
|
82
|
+
latency: measure === null || measure === void 0 ? void 0 : measure.duration,
|
|
83
|
+
resetReason: data === null || data === void 0 ? void 0 : data.resetReason,
|
|
84
|
+
ttlEnabled: data === null || data === void 0 ? void 0 : data.ttlEnabled
|
|
85
|
+
});
|
|
77
86
|
const {
|
|
78
87
|
doc,
|
|
79
88
|
version,
|
|
@@ -106,26 +115,31 @@ export class Channel extends Emitter {
|
|
|
106
115
|
this.emit('steps:added', data);
|
|
107
116
|
}
|
|
108
117
|
});
|
|
109
|
-
|
|
118
|
+
_defineProperty(this, "onOnlineHandler", () => {
|
|
119
|
+
// Force an immediate reconnect, the socket must first be closed to reset reconnection delay logic
|
|
120
|
+
if (!this.connected) {
|
|
121
|
+
var _this$socket2, _this$socket3;
|
|
122
|
+
(_this$socket2 = this.socket) === null || _this$socket2 === void 0 ? void 0 : _this$socket2.close();
|
|
123
|
+
(_this$socket3 = this.socket) === null || _this$socket3 === void 0 ? void 0 : _this$socket3.connect();
|
|
124
|
+
}
|
|
125
|
+
});
|
|
110
126
|
this.config = config;
|
|
127
|
+
this.analyticsHelper = analyticsHelper;
|
|
128
|
+
this.initExperience = createDocInitExp(this.analyticsHelper);
|
|
129
|
+
}
|
|
111
130
|
|
|
112
|
-
|
|
113
|
-
this.analyticsClient = config.analyticsClient;
|
|
114
|
-
}
|
|
115
|
-
} // read-only getters used for tests
|
|
116
|
-
|
|
131
|
+
// read-only getters used for tests
|
|
117
132
|
|
|
118
133
|
/**
|
|
119
134
|
* Connect to collab service using websockets
|
|
120
135
|
*/
|
|
121
136
|
connect() {
|
|
122
|
-
startMeasure(MEASURE_NAME.SOCKET_CONNECT);
|
|
123
|
-
|
|
137
|
+
startMeasure(MEASURE_NAME.SOCKET_CONNECT, this.analyticsHelper);
|
|
124
138
|
if (!this.initialized) {
|
|
125
|
-
|
|
126
|
-
this.
|
|
139
|
+
var _this$initExperience2;
|
|
140
|
+
startMeasure(MEASURE_NAME.DOCUMENT_INIT, this.analyticsHelper);
|
|
141
|
+
(_this$initExperience2 = this.initExperience) === null || _this$initExperience2 === void 0 ? void 0 : _this$initExperience2.start();
|
|
127
142
|
}
|
|
128
|
-
|
|
129
143
|
const {
|
|
130
144
|
documentAri,
|
|
131
145
|
url
|
|
@@ -137,11 +151,11 @@ export class Channel extends Emitter {
|
|
|
137
151
|
permissionTokenRefresh
|
|
138
152
|
} = this.config;
|
|
139
153
|
let authCb = null;
|
|
140
|
-
|
|
141
154
|
if (permissionTokenRefresh) {
|
|
142
155
|
authCb = cb => {
|
|
143
156
|
permissionTokenRefresh().then(token => {
|
|
144
|
-
cb({
|
|
157
|
+
cb({
|
|
158
|
+
// The permission token.
|
|
145
159
|
...(token ? {
|
|
146
160
|
token
|
|
147
161
|
} : {}),
|
|
@@ -164,11 +178,11 @@ export class Channel extends Emitter {
|
|
|
164
178
|
});
|
|
165
179
|
};
|
|
166
180
|
}
|
|
181
|
+
this.socket = createSocket(`${url}/session/${documentAri}`, authCb, this.config.productInfo);
|
|
167
182
|
|
|
168
|
-
|
|
183
|
+
// Due to https://github.com/socketio/socket.io-client/issues/1473,
|
|
169
184
|
// reconnect no longer fired on the socket.
|
|
170
185
|
// We should use `connect` for better cross platform compatibility(Mobile/Web).
|
|
171
|
-
|
|
172
186
|
this.socket.on('connect', this.onConnect);
|
|
173
187
|
this.socket.on('data', this.onReceiveData);
|
|
174
188
|
this.socket.on('steps:added', data => {
|
|
@@ -201,8 +215,9 @@ export class Channel extends Emitter {
|
|
|
201
215
|
});
|
|
202
216
|
this.socket.on('metadata:changed', payload => {
|
|
203
217
|
this.emit('metadata:changed', payload);
|
|
204
|
-
});
|
|
218
|
+
});
|
|
205
219
|
|
|
220
|
+
// ESS-2916 namespace status event - lock/unlock
|
|
206
221
|
this.socket.on('status', data => {
|
|
207
222
|
this.emit('status', data);
|
|
208
223
|
});
|
|
@@ -212,50 +227,48 @@ export class Channel extends Emitter {
|
|
|
212
227
|
this.emit('disconnect', {
|
|
213
228
|
reason
|
|
214
229
|
});
|
|
215
|
-
|
|
216
230
|
if (reason === 'io server disconnect' && this.socket) {
|
|
217
231
|
// The disconnection was initiated by the server, we need to reconnect manually.
|
|
218
|
-
|
|
232
|
+
try {
|
|
233
|
+
this.socket.connect();
|
|
234
|
+
} catch (error) {
|
|
235
|
+
var _this$analyticsHelper6;
|
|
236
|
+
(_this$analyticsHelper6 = this.analyticsHelper) === null || _this$analyticsHelper6 === void 0 ? void 0 : _this$analyticsHelper6.sendErrorEvent(error, 'Error while reconnecting the channel');
|
|
237
|
+
this.emit('error', {
|
|
238
|
+
message: 'Caught error during reconnection.',
|
|
239
|
+
data: {
|
|
240
|
+
status: 500,
|
|
241
|
+
code: 'RECONNECTION_ERROR'
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
}
|
|
219
245
|
}
|
|
220
|
-
});
|
|
246
|
+
});
|
|
221
247
|
|
|
248
|
+
// Socket error, including errors from `packetMiddleware`, `controllers` and `onconnect` and more. Parameter is a plain JSON object.
|
|
222
249
|
this.socket.on('error', error => {
|
|
223
250
|
this.emit('error', error);
|
|
224
|
-
});
|
|
225
|
-
// Ensure the error emit to the provider has the same structure, so we can handle them unified.
|
|
226
|
-
|
|
227
|
-
this.socket.on('connect_error', error => {
|
|
228
|
-
const measure = stopMeasure(MEASURE_NAME.SOCKET_CONNECT);
|
|
229
|
-
triggerAnalyticsEvent({
|
|
230
|
-
eventAction: EVENT_ACTION.CONNECTION,
|
|
231
|
-
attributes: {
|
|
232
|
-
eventStatus: EVENT_STATUS.FAILURE,
|
|
233
|
-
error: error,
|
|
234
|
-
latency: measure === null || measure === void 0 ? void 0 : measure.duration,
|
|
235
|
-
documentAri: this.config.documentAri
|
|
236
|
-
}
|
|
237
|
-
}, this.analyticsClient); // If error received with `data`, it means the connection is rejected
|
|
238
|
-
// by the server on purpose for example no permission, so no need to
|
|
239
|
-
// keep the underneath connection, need to close. But some error like
|
|
240
|
-
// `xhr polling error` needs to retry.
|
|
241
|
-
|
|
242
|
-
if (!!error.data) {
|
|
243
|
-
var _this$socket;
|
|
251
|
+
});
|
|
244
252
|
|
|
245
|
-
|
|
246
|
-
|
|
253
|
+
// `connect_error`'s parameter type is `Error`.
|
|
254
|
+
// Ensure the error emit to the provider has the same structure, so we can handle them unified.
|
|
255
|
+
this.socket.on('connect_error', this.onConnectError);
|
|
247
256
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
257
|
+
// To trigger reconnection when browser comes back online
|
|
258
|
+
if (!this.network) {
|
|
259
|
+
this.network = new Network({
|
|
260
|
+
onlineCallback: this.onOnlineHandler
|
|
251
261
|
});
|
|
252
|
-
}
|
|
253
|
-
}
|
|
262
|
+
}
|
|
254
263
|
|
|
264
|
+
// Helper class to track reconnection issues, to emit an error if too many failed reconnection attempts happen
|
|
265
|
+
this.reconnectHelper = new ReconnectHelper();
|
|
266
|
+
// Fired upon a reconnection attempt error (from Socket.IO Manager)
|
|
267
|
+
this.socket.io.on('reconnect_error', this.onReconnectError);
|
|
268
|
+
}
|
|
255
269
|
async fetchCatchup(fromVersion) {
|
|
256
270
|
try {
|
|
257
271
|
var _await$this$config$pe;
|
|
258
|
-
|
|
259
272
|
const {
|
|
260
273
|
doc,
|
|
261
274
|
version,
|
|
@@ -267,7 +280,8 @@ export class Channel extends Emitter {
|
|
|
267
280
|
version: fromVersion
|
|
268
281
|
},
|
|
269
282
|
requestInit: {
|
|
270
|
-
headers: {
|
|
283
|
+
headers: {
|
|
284
|
+
...(this.config.permissionTokenRefresh ? {
|
|
271
285
|
'x-token': (_await$this$config$pe = await this.config.permissionTokenRefresh()) !== null && _await$this$config$pe !== void 0 ? _await$this$config$pe : undefined
|
|
272
286
|
} : {}),
|
|
273
287
|
'x-product': getProduct(this.config.productInfo),
|
|
@@ -282,6 +296,17 @@ export class Channel extends Emitter {
|
|
|
282
296
|
metadata
|
|
283
297
|
};
|
|
284
298
|
} catch (error) {
|
|
299
|
+
if (error.code === 404) {
|
|
300
|
+
const errorNotFound = {
|
|
301
|
+
message: ErrorCodeMapper.documentNotFound.message,
|
|
302
|
+
data: {
|
|
303
|
+
status: error.code,
|
|
304
|
+
code: ErrorCodeMapper.documentNotFound.code
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
this.emit('error', errorNotFound);
|
|
308
|
+
return {};
|
|
309
|
+
}
|
|
285
310
|
logger("Can't fetch the catchup", error.message);
|
|
286
311
|
const errorCatchup = {
|
|
287
312
|
message: ErrorCodeMapper.catchupFail.message,
|
|
@@ -291,48 +316,44 @@ export class Channel extends Emitter {
|
|
|
291
316
|
}
|
|
292
317
|
};
|
|
293
318
|
this.emit('error', errorCatchup);
|
|
294
|
-
|
|
319
|
+
throw error;
|
|
295
320
|
}
|
|
296
321
|
}
|
|
322
|
+
|
|
297
323
|
/**
|
|
298
324
|
* Send message to service. Timestamp will be added server side.
|
|
299
325
|
*/
|
|
300
|
-
|
|
301
|
-
|
|
302
326
|
broadcast(type, data, callback) {
|
|
303
327
|
if (!this.connected || !this.socket) {
|
|
304
328
|
return;
|
|
305
329
|
}
|
|
306
|
-
|
|
307
330
|
this.socket.emit('broadcast', {
|
|
308
331
|
type,
|
|
309
332
|
...data
|
|
310
333
|
}, callback);
|
|
311
334
|
}
|
|
312
|
-
|
|
313
335
|
sendMetadata(metadata) {
|
|
314
336
|
if (!this.connected || !this.socket) {
|
|
315
337
|
return;
|
|
316
338
|
}
|
|
317
|
-
|
|
318
339
|
this.socket.emit('metadata', metadata);
|
|
319
340
|
}
|
|
320
|
-
|
|
321
341
|
sendPresenceJoined() {
|
|
322
342
|
if (!this.connected || !this.socket) {
|
|
323
343
|
return;
|
|
324
344
|
}
|
|
325
|
-
|
|
326
345
|
this.socket.emit('presence:joined');
|
|
327
346
|
}
|
|
328
|
-
|
|
329
347
|
disconnect() {
|
|
348
|
+
var _this$network;
|
|
330
349
|
this.unsubscribeAll();
|
|
331
|
-
|
|
350
|
+
(_this$network = this.network) === null || _this$network === void 0 ? void 0 : _this$network.destroy();
|
|
351
|
+
this.network = null;
|
|
332
352
|
if (this.socket) {
|
|
353
|
+
var _this$reconnectHelper3;
|
|
333
354
|
this.socket.close();
|
|
334
355
|
this.socket = null;
|
|
356
|
+
(_this$reconnectHelper3 = this.reconnectHelper) === null || _this$reconnectHelper3 === void 0 ? void 0 : _this$reconnectHelper3.destroy();
|
|
335
357
|
}
|
|
336
358
|
}
|
|
337
|
-
|
|
338
359
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
2
|
+
export let NetworkStatus;
|
|
3
|
+
(function (NetworkStatus) {
|
|
4
|
+
NetworkStatus["ONLINE"] = "ONLINE";
|
|
5
|
+
NetworkStatus["OFFLINE"] = "OFFLINE";
|
|
6
|
+
})(NetworkStatus || (NetworkStatus = {}));
|
|
7
|
+
export default class Network {
|
|
8
|
+
constructor(props) {
|
|
9
|
+
_defineProperty(this, "offlineHandler", () => {
|
|
10
|
+
this.status = NetworkStatus.OFFLINE;
|
|
11
|
+
});
|
|
12
|
+
_defineProperty(this, "onlineHandler", () => {
|
|
13
|
+
this.status = NetworkStatus.ONLINE;
|
|
14
|
+
if (this.onlineCallback) {
|
|
15
|
+
this.onlineCallback();
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
if (props !== null && props !== void 0 && props.initialStatus) {
|
|
19
|
+
this.status = props.initialStatus;
|
|
20
|
+
}
|
|
21
|
+
if (props !== null && props !== void 0 && props.onlineCallback) {
|
|
22
|
+
this.onlineCallback = props.onlineCallback;
|
|
23
|
+
}
|
|
24
|
+
window.addEventListener('offline', this.offlineHandler);
|
|
25
|
+
window.addEventListener('online', this.onlineHandler);
|
|
26
|
+
}
|
|
27
|
+
getStatus() {
|
|
28
|
+
return this.status || null;
|
|
29
|
+
}
|
|
30
|
+
destroy() {
|
|
31
|
+
window.removeEventListener('offline', this.offlineHandler);
|
|
32
|
+
window.removeEventListener('online', this.onlineHandler);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
2
|
+
import { NetworkStatus } from './network';
|
|
3
|
+
import { network } from './singleton';
|
|
4
|
+
|
|
5
|
+
// Calculation for max 8 offline reconnect attempts, with reconnection delay 200ms, randomization factor 0.5, reconnection delay max 128s
|
|
6
|
+
// Min: 800ms, Avg: 51s, Max: 6m
|
|
7
|
+
const FAILED_RECONNECTS_WHILE_OFFLINE_THRESHOLD = 8;
|
|
8
|
+
export default class ReconnectHelper {
|
|
9
|
+
constructor() {
|
|
10
|
+
_defineProperty(this, "failedReconnectCount", 0);
|
|
11
|
+
_defineProperty(this, "onlineHandler", () => {
|
|
12
|
+
this.failedReconnectCount = 0;
|
|
13
|
+
});
|
|
14
|
+
window.addEventListener('online', this.onlineHandler);
|
|
15
|
+
}
|
|
16
|
+
countReconnectError() {
|
|
17
|
+
// Only count the reconnection attempts when offline
|
|
18
|
+
if (network.getStatus() === NetworkStatus.OFFLINE) {
|
|
19
|
+
this.failedReconnectCount++;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
isLikelyNetworkIssue() {
|
|
23
|
+
const isLikelyNetworkIssue = this.failedReconnectCount >= FAILED_RECONNECTS_WHILE_OFFLINE_THRESHOLD;
|
|
24
|
+
return isLikelyNetworkIssue;
|
|
25
|
+
}
|
|
26
|
+
destroy() {
|
|
27
|
+
window.removeEventListener('online', this.onlineHandler);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -9,10 +9,9 @@ export const socketIOReasons = {
|
|
|
9
9
|
TRANSPORT_ERROR: 'transport error',
|
|
10
10
|
// The connection was closed (example: the user has lost connection, or the network was changed from WiFi to 4G)
|
|
11
11
|
PING_TIMEOUT: 'ping timeout' // The connection has encountered an error (example: the server was killed during a HTTP long-polling cycle)
|
|
12
|
-
|
|
13
12
|
};
|
|
14
|
-
export let DisconnectReason;
|
|
15
13
|
|
|
14
|
+
export let DisconnectReason;
|
|
16
15
|
(function (DisconnectReason) {
|
|
17
16
|
DisconnectReason["CLIENT_DISCONNECT"] = "CLIENT_DISCONNECT";
|
|
18
17
|
DisconnectReason["SERVER_DISCONNECT"] = "SERVER_DISCONNECT";
|
|
@@ -20,4 +19,20 @@ export let DisconnectReason;
|
|
|
20
19
|
DisconnectReason["SOCKET_ERROR"] = "SOCKET_ERROR";
|
|
21
20
|
DisconnectReason["SOCKET_TIMEOUT"] = "SOCKET_TIMEOUT";
|
|
22
21
|
DisconnectReason["UNKNOWN_DISCONNECT"] = "UNKNOWN_DISCONNECT";
|
|
23
|
-
})(DisconnectReason || (DisconnectReason = {}));
|
|
22
|
+
})(DisconnectReason || (DisconnectReason = {}));
|
|
23
|
+
export const disconnectedReasonMapper = reason => {
|
|
24
|
+
switch (reason) {
|
|
25
|
+
case socketIOReasons.IO_CLIENT_DISCONNECT:
|
|
26
|
+
return DisconnectReason.CLIENT_DISCONNECT;
|
|
27
|
+
case socketIOReasons.IO_SERVER_DISCONNECT:
|
|
28
|
+
return DisconnectReason.SERVER_DISCONNECT;
|
|
29
|
+
case socketIOReasons.TRANSPORT_CLOSED:
|
|
30
|
+
return DisconnectReason.SOCKET_CLOSED;
|
|
31
|
+
case socketIOReasons.TRANSPORT_ERROR:
|
|
32
|
+
return DisconnectReason.SOCKET_ERROR;
|
|
33
|
+
case socketIOReasons.PING_TIMEOUT:
|
|
34
|
+
return DisconnectReason.SOCKET_TIMEOUT;
|
|
35
|
+
default:
|
|
36
|
+
return DisconnectReason.UNKNOWN_DISCONNECT;
|
|
37
|
+
}
|
|
38
|
+
};
|
package/dist/es2019/emitter.js
CHANGED
|
@@ -4,7 +4,6 @@ export class Emitter {
|
|
|
4
4
|
constructor() {
|
|
5
5
|
_defineProperty(this, "eventEmitter", new EventEmitter2());
|
|
6
6
|
}
|
|
7
|
-
|
|
8
7
|
/**
|
|
9
8
|
* Emit events to subscribers
|
|
10
9
|
*/
|
|
@@ -12,32 +11,28 @@ export class Emitter {
|
|
|
12
11
|
this.eventEmitter.emit(evt, data);
|
|
13
12
|
return this;
|
|
14
13
|
}
|
|
14
|
+
|
|
15
15
|
/**
|
|
16
16
|
* Subscribe to events emitted by this provider
|
|
17
17
|
*/
|
|
18
|
-
|
|
19
|
-
|
|
20
18
|
on(evt, handler) {
|
|
21
19
|
this.eventEmitter.on(evt, handler);
|
|
22
20
|
return this;
|
|
23
21
|
}
|
|
22
|
+
|
|
24
23
|
/**
|
|
25
24
|
* Unsubscribe from events emitted by this provider
|
|
26
25
|
*/
|
|
27
|
-
|
|
28
|
-
|
|
29
26
|
off(evt, handler) {
|
|
30
27
|
this.eventEmitter.off(evt, handler);
|
|
31
28
|
return this;
|
|
32
29
|
}
|
|
30
|
+
|
|
33
31
|
/**
|
|
34
32
|
* Unsubscribe from all events emitted by this provider.
|
|
35
33
|
*/
|
|
36
|
-
|
|
37
|
-
|
|
38
34
|
unsubscribeAll(evt) {
|
|
39
35
|
this.eventEmitter.removeAllListeners(evt);
|
|
40
36
|
return this;
|
|
41
37
|
}
|
|
42
|
-
|
|
43
38
|
}
|
|
@@ -23,31 +23,33 @@ export const ErrorCodeMapper = {
|
|
|
23
23
|
code: 'FAIL_TO_SAVE',
|
|
24
24
|
message: 'Collab service is not able to save changes'
|
|
25
25
|
},
|
|
26
|
+
restoreError: {
|
|
27
|
+
code: 'DOCUMENT_RESTORE_ERROR',
|
|
28
|
+
message: 'Collab service unable to restore document'
|
|
29
|
+
},
|
|
26
30
|
internalError: {
|
|
27
31
|
code: 'INTERNAL_SERVICE_ERROR',
|
|
28
|
-
message: 'Collab service has
|
|
32
|
+
message: 'Collab service has experienced an internal server error'
|
|
29
33
|
}
|
|
30
34
|
};
|
|
31
35
|
export const errorCodeMapper = error => {
|
|
32
36
|
var _error$data;
|
|
33
|
-
|
|
34
37
|
switch ((_error$data = error.data) === null || _error$data === void 0 ? void 0 : _error$data.code) {
|
|
35
38
|
case 'INSUFFICIENT_EDITING_PERMISSION':
|
|
36
39
|
return {
|
|
37
40
|
status: 403,
|
|
38
41
|
code: ErrorCodeMapper.noPermissionError.code,
|
|
39
42
|
message: ErrorCodeMapper.noPermissionError.message,
|
|
40
|
-
reason:
|
|
43
|
+
reason:
|
|
44
|
+
// Typescript magic so it detects the union type
|
|
41
45
|
typeof error.data.meta === 'object' ? error.data.meta.reason : undefined
|
|
42
46
|
};
|
|
43
|
-
|
|
44
47
|
case 'DOCUMENT_NOT_FOUND':
|
|
45
48
|
return {
|
|
46
49
|
status: 404,
|
|
47
50
|
code: ErrorCodeMapper.documentNotFound.code,
|
|
48
51
|
message: ErrorCodeMapper.documentNotFound.message
|
|
49
52
|
};
|
|
50
|
-
|
|
51
53
|
case 'FAILED_ON_S3':
|
|
52
54
|
case 'DYNAMO_ERROR':
|
|
53
55
|
return {
|
|
@@ -55,7 +57,13 @@ export const errorCodeMapper = error => {
|
|
|
55
57
|
code: ErrorCodeMapper.failToSave.code,
|
|
56
58
|
message: ErrorCodeMapper.failToSave.message
|
|
57
59
|
};
|
|
58
|
-
|
|
60
|
+
case 'DOCUMENT_RESTORE_ERROR':
|
|
61
|
+
return {
|
|
62
|
+
status: 500,
|
|
63
|
+
code: ErrorCodeMapper.restoreError.code,
|
|
64
|
+
message: ErrorCodeMapper.restoreError.message
|
|
65
|
+
};
|
|
66
|
+
// Temporarily re-added so we don't emit errors to Confluence by default as they will disconnect the collab provider
|
|
59
67
|
case 'CATCHUP_FAILED':
|
|
60
68
|
case 'GET_QUERY_TIME_OUT':
|
|
61
69
|
case 'INIT_DATA_LOAD_FAILED':
|
|
@@ -64,7 +72,6 @@ export const errorCodeMapper = error => {
|
|
|
64
72
|
code: ErrorCodeMapper.internalError.code,
|
|
65
73
|
message: ErrorCodeMapper.internalError.message
|
|
66
74
|
};
|
|
67
|
-
|
|
68
75
|
default:
|
|
69
76
|
break;
|
|
70
77
|
}
|
|
@@ -1,26 +1,24 @@
|
|
|
1
1
|
const defaultNCSFeatureFlags = {
|
|
2
2
|
testFF: false
|
|
3
3
|
};
|
|
4
|
+
|
|
4
5
|
/**
|
|
5
6
|
* Note that Confluence should have the same FF sets as NCS
|
|
6
7
|
*/
|
|
7
|
-
|
|
8
8
|
const productKeys = {
|
|
9
9
|
confluence: {
|
|
10
10
|
testFF: 'confluence.fe.collab.provider.testFF'
|
|
11
11
|
}
|
|
12
12
|
};
|
|
13
|
-
|
|
14
13
|
const filterFeatureFlagNames = flags => {
|
|
15
14
|
const pairs = Object.entries(flags);
|
|
16
15
|
return pairs.filter(([_key, value]) => !!value).map(([key]) => key);
|
|
17
16
|
};
|
|
17
|
+
|
|
18
18
|
/**
|
|
19
19
|
* Takes a record of {NCS Feature Flag Names → boolean} and a supported product name.
|
|
20
20
|
* Returns the corresponding product’s Launch Darkly Keys for each of the flags set as true in the input record.
|
|
21
21
|
* */
|
|
22
|
-
|
|
23
|
-
|
|
24
22
|
export const getProductSpecificFeatureFlags = (flags, product) => {
|
|
25
23
|
const ncsFeatureFlags = filterFeatureFlagNames(flags);
|
|
26
24
|
return ncsFeatureFlags.map(key => productKeys[product][key]);
|
|
@@ -29,6 +27,5 @@ export function getCollabProviderFeatureFlag(flagName, featureFlags) {
|
|
|
29
27
|
if (featureFlags) {
|
|
30
28
|
return flagName in featureFlags ? featureFlags[flagName] : defaultNCSFeatureFlags[flagName];
|
|
31
29
|
}
|
|
32
|
-
|
|
33
30
|
return defaultNCSFeatureFlags[flagName];
|
|
34
31
|
}
|