@atlaskit/collab-provider 8.4.0 → 8.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/dist/cjs/analytics/{index.js → analytics-helper.js} +43 -4
  3. package/dist/cjs/analytics/performance.js +6 -5
  4. package/dist/cjs/channel.js +222 -224
  5. package/dist/cjs/{provider → document}/catchup.js +2 -2
  6. package/dist/cjs/document/document-service.js +617 -0
  7. package/dist/cjs/document/step-queue-state.js +51 -0
  8. package/dist/cjs/errors/error-code-mapper.js +86 -67
  9. package/dist/cjs/errors/error-types.js +251 -21
  10. package/dist/cjs/helpers/utils.js +1 -12
  11. package/dist/cjs/participants/participants-helper.js +51 -0
  12. package/dist/cjs/participants/participants-service.js +217 -0
  13. package/dist/cjs/participants/participants-state.js +53 -0
  14. package/dist/cjs/{provider/telepointers.js → participants/telepointers-helper.js} +6 -6
  15. package/dist/cjs/provider/commit-step.js +4 -4
  16. package/dist/cjs/provider/index.js +212 -774
  17. package/dist/cjs/types.js +3 -0
  18. package/dist/cjs/version-wrapper.js +1 -1
  19. package/dist/cjs/version.json +1 -1
  20. package/dist/es2019/analytics/{index.js → analytics-helper.js} +15 -4
  21. package/dist/es2019/analytics/performance.js +5 -6
  22. package/dist/es2019/channel.js +123 -116
  23. package/dist/es2019/{provider → document}/catchup.js +2 -2
  24. package/dist/es2019/document/document-service.js +495 -0
  25. package/dist/es2019/document/step-queue-state.js +30 -0
  26. package/dist/es2019/errors/error-code-mapper.js +87 -63
  27. package/dist/es2019/errors/error-types.js +143 -5
  28. package/dist/es2019/helpers/utils.js +0 -10
  29. package/dist/es2019/participants/participants-helper.js +25 -0
  30. package/dist/es2019/participants/participants-service.js +166 -0
  31. package/dist/es2019/participants/participants-state.js +28 -0
  32. package/dist/es2019/{provider/telepointers.js → participants/telepointers-helper.js} +2 -2
  33. package/dist/es2019/provider/commit-step.js +4 -4
  34. package/dist/es2019/provider/index.js +162 -637
  35. package/dist/es2019/types.js +4 -0
  36. package/dist/es2019/version-wrapper.js +1 -1
  37. package/dist/es2019/version.json +1 -1
  38. package/dist/esm/analytics/{index.js → analytics-helper.js} +43 -4
  39. package/dist/esm/analytics/performance.js +5 -6
  40. package/dist/esm/channel.js +224 -226
  41. package/dist/esm/{provider → document}/catchup.js +2 -2
  42. package/dist/esm/document/document-service.js +609 -0
  43. package/dist/esm/document/step-queue-state.js +43 -0
  44. package/dist/esm/errors/error-code-mapper.js +87 -64
  45. package/dist/esm/errors/error-types.js +243 -18
  46. package/dist/esm/helpers/utils.js +0 -10
  47. package/dist/esm/participants/participants-helper.js +43 -0
  48. package/dist/esm/participants/participants-service.js +209 -0
  49. package/dist/esm/participants/participants-state.js +45 -0
  50. package/dist/esm/{provider/telepointers.js → participants/telepointers-helper.js} +4 -4
  51. package/dist/esm/provider/commit-step.js +4 -4
  52. package/dist/esm/provider/index.js +212 -774
  53. package/dist/esm/types.js +4 -0
  54. package/dist/esm/version-wrapper.js +1 -1
  55. package/dist/esm/version.json +1 -1
  56. package/dist/types/analytics/{index.d.ts → analytics-helper.d.ts} +3 -1
  57. package/dist/types/analytics/performance.d.ts +3 -1
  58. package/dist/types/analytics/ufo.d.ts +1 -1
  59. package/dist/types/channel.d.ts +12 -5
  60. package/dist/types/document/document-service.d.ts +105 -0
  61. package/dist/types/document/step-queue-state.d.ts +16 -0
  62. package/dist/types/errors/error-code-mapper.d.ts +2 -36
  63. package/dist/types/errors/error-types.d.ts +439 -4
  64. package/dist/types/helpers/const.d.ts +2 -2
  65. package/dist/types/helpers/utils.d.ts +0 -6
  66. package/dist/types/index.d.ts +2 -1
  67. package/dist/types/participants/participants-helper.d.ts +15 -0
  68. package/dist/types/participants/participants-service.d.ts +70 -0
  69. package/dist/types/participants/participants-state.d.ts +13 -0
  70. package/dist/types/participants/telepointers-helper.d.ts +4 -0
  71. package/dist/types/provider/commit-step.d.ts +6 -6
  72. package/dist/types/provider/index.d.ts +80 -73
  73. package/dist/types/types.d.ts +47 -30
  74. package/package.json +4 -4
  75. package/report.api.md +172 -19
  76. package/dist/types/provider/telepointers.d.ts +0 -5
  77. /package/dist/types/{provider → document}/catchup.d.ts +0 -0
@@ -1,78 +1,102 @@
1
- export const ErrorCodeMapper = {
2
- noPermissionError: {
3
- code: 'NO_PERMISSION_ERROR',
4
- message: 'User does not have permissions to access this document or document is not found'
5
- },
6
- documentNotFound: {
7
- code: 'DOCUMENT_NOT_FOUND',
8
- message: 'The requested document is not found'
9
- },
10
- hasToLogin: {
11
- code: 'HAS_TO_LOGIN',
12
- message: 'The user needs to login'
13
- },
14
- catchupFail: {
15
- code: 'CATCHUP_FAILED',
16
- message: 'Cannot fetch catchup from collab service'
17
- },
18
- serviceUnvailable: {
19
- code: 'SERVICE_UNAVAILABLE',
20
- message: 'Service is not available'
21
- },
22
- failToSave: {
23
- code: 'FAIL_TO_SAVE',
24
- message: 'Collab service is not able to save changes'
25
- },
26
- restoreError: {
27
- code: 'DOCUMENT_RESTORE_ERROR',
28
- message: 'Collab service unable to restore document'
29
- },
30
- internalError: {
31
- code: 'INTERNAL_SERVICE_ERROR',
32
- message: 'Collab service has experienced an internal server error'
33
- }
34
- };
1
+ import { NCS_ERROR_CODE } from './error-types';
2
+ import { INTERNAL_ERROR_CODE, PROVIDER_ERROR_CODE } from './error-types';
3
+
4
+ /*
5
+ * Maps internal collab provider errors to an emitted error format
6
+ */
35
7
  export const errorCodeMapper = error => {
36
- var _error$data;
8
+ var _error$data, _error$data2, _error$data3;
37
9
  switch ((_error$data = error.data) === null || _error$data === void 0 ? void 0 : _error$data.code) {
38
- case 'INSUFFICIENT_EDITING_PERMISSION':
10
+ case NCS_ERROR_CODE.HEAD_VERSION_UPDATE_FAILED:
11
+ case NCS_ERROR_CODE.VERSION_NUMBER_ALREADY_EXISTS:
12
+ // This should never be called with these errors
13
+ return;
14
+ case INTERNAL_ERROR_CODE.ADD_STEPS_ERROR:
15
+ case INTERNAL_ERROR_CODE.RECONNECTION_ERROR:
16
+ case INTERNAL_ERROR_CODE.CONNECTION_ERROR:
17
+ // These errors shouldn't be emitted, we're hoping the provider self-recovers over time
18
+ return;
19
+ case NCS_ERROR_CODE.INSUFFICIENT_EDITING_PERMISSION:
20
+ case INTERNAL_ERROR_CODE.TOKEN_PERMISSION_ERROR:
21
+ return {
22
+ code: PROVIDER_ERROR_CODE.NO_PERMISSION_ERROR,
23
+ message: 'User does not have permissions to access this document or document is not found',
24
+ reason: error.data.meta.reason,
25
+ recoverable: true,
26
+ status: 403
27
+ };
28
+ case NCS_ERROR_CODE.FORBIDDEN_USER_TOKEN:
29
+ return {
30
+ code: PROVIDER_ERROR_CODE.INVALID_USER_TOKEN,
31
+ message: 'The user token was invalid',
32
+ recoverable: true,
33
+ status: 403
34
+ };
35
+ case INTERNAL_ERROR_CODE.DOCUMENT_NOT_FOUND:
36
+ return {
37
+ code: PROVIDER_ERROR_CODE.DOCUMENT_NOT_FOUND,
38
+ message: 'The requested document is not found',
39
+ recoverable: true,
40
+ status: 404
41
+ };
42
+ case NCS_ERROR_CODE.TENANT_INSTANCE_MAINTENANCE:
43
+ case NCS_ERROR_CODE.LOCKED_DOCUMENT:
44
+ return {
45
+ code: PROVIDER_ERROR_CODE.LOCKED,
46
+ message: 'The document is currently not available, please try again later',
47
+ recoverable: true
48
+ };
49
+ case NCS_ERROR_CODE.DYNAMO_ERROR:
50
+ return {
51
+ code: PROVIDER_ERROR_CODE.FAIL_TO_SAVE,
52
+ message: 'Collab service is not able to save changes',
53
+ recoverable: false,
54
+ status: 500
55
+ };
56
+ case INTERNAL_ERROR_CODE.DOCUMENT_RESTORE_ERROR:
39
57
  return {
40
- status: 403,
41
- code: ErrorCodeMapper.noPermissionError.code,
42
- message: ErrorCodeMapper.noPermissionError.message,
43
- reason:
44
- // Typescript magic so it detects the union type
45
- typeof error.data.meta === 'object' ? error.data.meta.reason : undefined
58
+ code: PROVIDER_ERROR_CODE.DOCUMENT_RESTORE_ERROR,
59
+ message: 'Collab service unable to restore document',
60
+ recoverable: false,
61
+ status: 500
46
62
  };
47
- case 'DOCUMENT_NOT_FOUND':
63
+ case NCS_ERROR_CODE.INIT_DATA_LOAD_FAILED:
48
64
  return {
49
- status: 404,
50
- code: ErrorCodeMapper.documentNotFound.code,
51
- message: ErrorCodeMapper.documentNotFound.message
65
+ code: PROVIDER_ERROR_CODE.INITIALISATION_ERROR,
66
+ message: "The initial document couldn't be loaded from the collab service",
67
+ recoverable: false,
68
+ status: 500
52
69
  };
53
- case 'FAILED_ON_S3':
54
- case 'DYNAMO_ERROR':
70
+ case INTERNAL_ERROR_CODE.RECONNECTION_NETWORK_ISSUE:
55
71
  return {
56
- status: 500,
57
- code: ErrorCodeMapper.failToSave.code,
58
- message: ErrorCodeMapper.failToSave.message
72
+ code: PROVIDER_ERROR_CODE.NETWORK_ISSUE,
73
+ message: "Couldn't reconnect to the collab service due to network issues",
74
+ recoverable: true,
75
+ status: 500
59
76
  };
60
- case 'DOCUMENT_RESTORE_ERROR':
77
+ case NCS_ERROR_CODE.NAMESPACE_INVALID:
78
+ case NCS_ERROR_CODE.INVALID_ACTIVATION_ID:
79
+ case NCS_ERROR_CODE.INVALID_DOCUMENT_ARI:
80
+ case NCS_ERROR_CODE.INVALID_CLOUD_ID:
61
81
  return {
62
- status: 500,
63
- code: ErrorCodeMapper.restoreError.code,
64
- message: ErrorCodeMapper.restoreError.message
82
+ code: PROVIDER_ERROR_CODE.INVALID_PROVIDER_CONFIGURATION,
83
+ message: 'Invalid provider configuration',
84
+ recoverable: false,
85
+ reason: (_error$data2 = error.data) === null || _error$data2 === void 0 ? void 0 : _error$data2.code,
86
+ status: 400
65
87
  };
66
- // Temporarily re-added so we don't emit errors to Confluence by default as they will disconnect the collab provider
67
- case 'CATCHUP_FAILED':
68
- case 'GET_QUERY_TIME_OUT':
69
- case 'INIT_DATA_LOAD_FAILED':
88
+ case NCS_ERROR_CODE.NAMESPACE_NOT_FOUND:
89
+ case NCS_ERROR_CODE.ERROR_MAPPING_ERROR:
90
+ case NCS_ERROR_CODE.EMPTY_BROADCAST:
91
+ case INTERNAL_ERROR_CODE.CATCHUP_FAILED:
70
92
  return {
71
- status: 500,
72
- code: ErrorCodeMapper.internalError.code,
73
- message: ErrorCodeMapper.internalError.message
93
+ code: PROVIDER_ERROR_CODE.INTERNAL_SERVICE_ERROR,
94
+ message: 'Collab Provider experienced an unrecoverable error',
95
+ recoverable: false,
96
+ reason: (_error$data3 = error.data) === null || _error$data3 === void 0 ? void 0 : _error$data3.code,
97
+ status: 500
74
98
  };
75
99
  default:
76
- break;
100
+ return;
77
101
  }
78
102
  };
@@ -1,13 +1,151 @@
1
1
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
- export class NotConnectedError extends Error {
3
- constructor(message) {
2
+ // Internal error codes (generated by collab provider)
3
+ export let INTERNAL_ERROR_CODE;
4
+ // NCS error coded (generated by NCS)
5
+ (function (INTERNAL_ERROR_CODE) {
6
+ INTERNAL_ERROR_CODE["TOKEN_PERMISSION_ERROR"] = "TOKEN_PERMISSION_ERROR";
7
+ INTERNAL_ERROR_CODE["RECONNECTION_NETWORK_ISSUE"] = "RECONNECTION_NETWORK_ISSUE";
8
+ INTERNAL_ERROR_CODE["CONNECTION_ERROR"] = "CONNECTION_ERROR";
9
+ INTERNAL_ERROR_CODE["RECONNECTION_ERROR"] = "RECONNECTION_ERROR";
10
+ INTERNAL_ERROR_CODE["DOCUMENT_NOT_FOUND"] = "DOCUMENT_NOT_FOUND";
11
+ INTERNAL_ERROR_CODE["CATCHUP_FAILED"] = "CATCHUP_FAILED";
12
+ INTERNAL_ERROR_CODE["DOCUMENT_RESTORE_ERROR"] = "DOCUMENT_RESTORE_ERROR";
13
+ INTERNAL_ERROR_CODE["ADD_STEPS_ERROR"] = "ADD_STEPS_ERROR";
14
+ })(INTERNAL_ERROR_CODE || (INTERNAL_ERROR_CODE = {}));
15
+ export let NCS_ERROR_CODE;
16
+
17
+ // TODO: Import emitted error codes from NCS
18
+
19
+ // NCS Errors
20
+ // - Step rejection errors
21
+ (function (NCS_ERROR_CODE) {
22
+ NCS_ERROR_CODE["HEAD_VERSION_UPDATE_FAILED"] = "HEAD_VERSION_UPDATE_FAILED";
23
+ NCS_ERROR_CODE["VERSION_NUMBER_ALREADY_EXISTS"] = "VERSION_NUMBER_ALREADY_EXISTS";
24
+ NCS_ERROR_CODE["INSUFFICIENT_EDITING_PERMISSION"] = "INSUFFICIENT_EDITING_PERMISSION";
25
+ NCS_ERROR_CODE["FORBIDDEN_USER_TOKEN"] = "FORBIDDEN_USER_TOKEN";
26
+ NCS_ERROR_CODE["DOCUMENT_NOT_FOUND"] = "DOCUMENT_NOT_FOUND";
27
+ NCS_ERROR_CODE["INIT_DATA_LOAD_FAILED"] = "INIT_DATA_LOAD_FAILED";
28
+ NCS_ERROR_CODE["ERROR_MAPPING_ERROR"] = "ERROR_MAPPING_ERROR";
29
+ NCS_ERROR_CODE["NAMESPACE_INVALID"] = "NAMESPACE_INVALID";
30
+ NCS_ERROR_CODE["NAMESPACE_NOT_FOUND"] = "NAMESPACE_NOT_FOUND";
31
+ NCS_ERROR_CODE["TENANT_INSTANCE_MAINTENANCE"] = "TENANT_INSTANCE_MAINTENANCE";
32
+ NCS_ERROR_CODE["LOCKED_DOCUMENT"] = "LOCKED_DOCUMENT";
33
+ NCS_ERROR_CODE["EMPTY_BROADCAST"] = "EMPTY_BROADCAST";
34
+ NCS_ERROR_CODE["DYNAMO_ERROR"] = "DYNAMO_ERROR";
35
+ NCS_ERROR_CODE["INVALID_ACTIVATION_ID"] = "INVALID_ACTIVATION_ID";
36
+ NCS_ERROR_CODE["INVALID_DOCUMENT_ARI"] = "INVALID_DOCUMENT_ARI";
37
+ NCS_ERROR_CODE["INVALID_CLOUD_ID"] = "INVALID_CLOUD_ID";
38
+ })(NCS_ERROR_CODE || (NCS_ERROR_CODE = {}));
39
+ // Emitted errors
40
+ export let PROVIDER_ERROR_CODE;
41
+
42
+ /*
43
+ * This is what a generic ProviderError type would look like:
44
+ * type ProviderError = {
45
+ * // Unique code, identifies the specific emitted error
46
+ * // Also exposed as a PROVIDER_ERROR_CODE enum to allow subscribers to use them
47
+ * code: PROVIDER_ERROR_CODE;
48
+ * // Informative message describing what went wrong
49
+ * message: string;
50
+ * // Flag indicating whether an error is recoverable or not
51
+ * // used by consumers to disable the provider and show an error message
52
+ * recoverable: boolean;
53
+ * // A reason code used to give more detail about why a certain error was thrown
54
+ * reason?: string;
55
+ * }
56
+ */
57
+
58
+ /**
59
+ * This occurs when the provided user token is considered invalid for the given document ARI.
60
+ * It happens during initialisation of the provider.
61
+ * It could mean the document has been deleted (hence not found).
62
+ * @message Message returned to editor, i.e User does not have permissions to access this document or document is not found
63
+ * @recoverable It is recoverable, as we will try to refresh the token.
64
+ */
65
+ (function (PROVIDER_ERROR_CODE) {
66
+ PROVIDER_ERROR_CODE["NO_PERMISSION_ERROR"] = "NO_PERMISSION_ERROR";
67
+ PROVIDER_ERROR_CODE["INVALID_USER_TOKEN"] = "INVALID_USER_TOKEN";
68
+ PROVIDER_ERROR_CODE["DOCUMENT_NOT_FOUND"] = "DOCUMENT_NOT_FOUND";
69
+ PROVIDER_ERROR_CODE["LOCKED"] = "LOCKED";
70
+ PROVIDER_ERROR_CODE["FAIL_TO_SAVE"] = "FAIL_TO_SAVE";
71
+ PROVIDER_ERROR_CODE["DOCUMENT_RESTORE_ERROR"] = "DOCUMENT_RESTORE_ERROR";
72
+ PROVIDER_ERROR_CODE["INITIALISATION_ERROR"] = "INITIALISATION_ERROR";
73
+ PROVIDER_ERROR_CODE["NETWORK_ISSUE"] = "NETWORK_ISSUE";
74
+ PROVIDER_ERROR_CODE["INVALID_PROVIDER_CONFIGURATION"] = "INVALID_PROVIDER_CONFIGURATION";
75
+ PROVIDER_ERROR_CODE["INTERNAL_SERVICE_ERROR"] = "INTERNAL_SERVICE_ERROR";
76
+ })(PROVIDER_ERROR_CODE || (PROVIDER_ERROR_CODE = {}));
77
+ // Custom Errors
78
+ class CustomError extends Error {
79
+ constructor(message, error) {
4
80
  super(message);
81
+ if (typeof (error === null || error === void 0 ? void 0 : error.message) === 'string') {
82
+ this.message = error.message;
83
+ }
84
+ }
85
+ toJSON() {
86
+ return {
87
+ name: this.name,
88
+ message: this.message
89
+ };
90
+ }
91
+ }
92
+ export class NotConnectedError extends CustomError {
93
+ constructor(...args) {
94
+ super(...args);
5
95
  _defineProperty(this, "name", 'NotConnectedError');
6
96
  }
7
97
  }
8
- export class NotInitializedError extends Error {
9
- constructor(message) {
10
- super(message);
98
+ export class NotInitializedError extends CustomError {
99
+ constructor(...args) {
100
+ super(...args);
11
101
  _defineProperty(this, "name", 'NotInitializedError');
12
102
  }
103
+ }
104
+ export class ProviderInitialisationError extends CustomError {
105
+ constructor(...args) {
106
+ super(...args);
107
+ _defineProperty(this, "name", 'ProviderInitialisationError');
108
+ }
109
+ }
110
+ export class SendTransactionError extends CustomError {
111
+ constructor(...args) {
112
+ super(...args);
113
+ _defineProperty(this, "name", 'SendTransactionError');
114
+ }
115
+ }
116
+ export class DestroyError extends CustomError {
117
+ constructor(...args) {
118
+ super(...args);
119
+ _defineProperty(this, "name", 'DestroyError');
120
+ }
121
+ }
122
+ export class SetTitleError extends CustomError {
123
+ constructor(...args) {
124
+ super(...args);
125
+ _defineProperty(this, "name", 'SetTitleError');
126
+ }
127
+ }
128
+ export class SetEditorWidthError extends CustomError {
129
+ constructor(...args) {
130
+ super(...args);
131
+ _defineProperty(this, "name", 'SetEditorWidthError');
132
+ }
133
+ }
134
+ export class SetMetadataError extends CustomError {
135
+ constructor(...args) {
136
+ super(...args);
137
+ _defineProperty(this, "name", 'SetMetadataError');
138
+ }
139
+ }
140
+ export class GetCurrentStateError extends CustomError {
141
+ constructor(...args) {
142
+ super(...args);
143
+ _defineProperty(this, "name", 'GetCurrentStateError');
144
+ }
145
+ }
146
+ export class GetFinalAcknowledgedStateError extends CustomError {
147
+ constructor(...args) {
148
+ super(...args);
149
+ _defineProperty(this, "name", 'GetFinalAcknowledgedStateError');
150
+ }
13
151
  }
@@ -4,16 +4,6 @@ export const createLogger = (prefix, color = 'blue') => (msg, data = null) => {
4
4
  console.log(`%cCollab-${prefix}: ${msg}`, `color: ${color}; font-weight: bold`, data);
5
5
  }
6
6
  };
7
- const logger = createLogger('Helper:util', 'black');
8
- export const getParticipant = userId => {
9
- logger('getParticipant: ', userId);
10
- return {
11
- userId: userId,
12
- name: userId,
13
- avatar: '',
14
- email: `${userId.replace(/\s/g, '').toLocaleLowerCase()}@atlassian.com`
15
- };
16
- };
17
7
  export function sleep(ms) {
18
8
  return new Promise(resolve => {
19
9
  setTimeout(resolve, ms);
@@ -0,0 +1,25 @@
1
+ export const PARTICIPANT_UPDATE_INTERVAL = 300 * 1000; // 300 seconds
2
+
3
+ export const createParticipantFromPayload = async (payload, getUser) => {
4
+ var _user, _user2, _user3;
5
+ const {
6
+ sessionId,
7
+ timestamp,
8
+ clientId,
9
+ userId
10
+ } = payload;
11
+ let user;
12
+ if (getUser) {
13
+ user = await getUser(userId);
14
+ }
15
+ const participant = {
16
+ name: ((_user = user) === null || _user === void 0 ? void 0 : _user.name) || '',
17
+ email: ((_user2 = user) === null || _user2 === void 0 ? void 0 : _user2.email) || '',
18
+ avatar: ((_user3 = user) === null || _user3 === void 0 ? void 0 : _user3.avatar) || '',
19
+ sessionId,
20
+ lastActive: timestamp,
21
+ userId,
22
+ clientId
23
+ };
24
+ return participant;
25
+ };
@@ -0,0 +1,166 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+ import { disconnectedReasonMapper } from '../disconnected-reason-mapper';
3
+ import { EVENT_ACTION, EVENT_STATUS } from '../helpers/const';
4
+ import { telepointerFromStep } from './telepointers-helper';
5
+ import { createParticipantFromPayload as enrichParticipant, PARTICIPANT_UPDATE_INTERVAL } from './participants-helper';
6
+ import { ParticipantsState } from './participants-state';
7
+ export class ParticipantsService {
8
+ constructor(analyticsHelper, participantsState = new ParticipantsState()) {
9
+ _defineProperty(this, "updateParticipant", async (payload, getUser, emit) => {
10
+ const {
11
+ userId
12
+ } = payload;
13
+
14
+ // If userId does not exist, does nothing here to prevent duplication.
15
+ if (!userId) {
16
+ return;
17
+ }
18
+ let participant;
19
+ // getUser is a failable callback, hence try-catch
20
+ try {
21
+ participant = await enrichParticipant(
22
+ // userId _must_ be defined, this lets the compiler know
23
+ {
24
+ ...payload,
25
+ userId
26
+ }, getUser);
27
+ } catch (error) {
28
+ var _this$analyticsHelper;
29
+ // We don't want to throw errors for Presence features as they tend to self-restore
30
+ (_this$analyticsHelper = this.analyticsHelper) === null || _this$analyticsHelper === void 0 ? void 0 : _this$analyticsHelper.sendErrorEvent(error, 'enriching participant');
31
+ }
32
+ if (!participant) {
33
+ return;
34
+ }
35
+ const isNewParticipant = this.participantsState.doesntHave(participant.sessionId);
36
+ this.participantsState.setBySessionId(participant.sessionId, participant);
37
+ if (!isNewParticipant) {
38
+ return;
39
+ }
40
+ this.emitPresence({
41
+ joined: [participant]
42
+ }, emit, 'handling participant updated event');
43
+ });
44
+ _defineProperty(this, "participantLeft", ({
45
+ sessionId
46
+ }, emit) => {
47
+ this.participantsState.removeBySessionId(sessionId);
48
+ this.emitPresence({
49
+ left: [{
50
+ sessionId
51
+ }]
52
+ }, emit, 'participant leaving');
53
+ });
54
+ _defineProperty(this, "disconnect", (reason, sessionId, emit) => {
55
+ const left = this.participantsState.getParticipants();
56
+ this.participantsState.clear();
57
+ try {
58
+ emit('disconnected', {
59
+ reason: disconnectedReasonMapper(reason),
60
+ sid: sessionId
61
+ });
62
+ } catch (error) {
63
+ var _this$analyticsHelper2;
64
+ // We don't want to throw errors for Presence features as they tend to self-restore
65
+ (_this$analyticsHelper2 = this.analyticsHelper) === null || _this$analyticsHelper2 === void 0 ? void 0 : _this$analyticsHelper2.sendErrorEvent(error, 'emitting disconnected data');
66
+ }
67
+ if (left.length) {
68
+ this.emitPresence({
69
+ left
70
+ }, emit, 'emitting presence update on disconnect');
71
+ }
72
+ });
73
+ _defineProperty(this, "updateLastActive", (userIds = []) => this.participantsState.updateLastActive(Date.now(), userIds));
74
+ _defineProperty(this, "participantTelepointer", (payload, thisSessionId, getUser, emit) => {
75
+ const {
76
+ sessionId,
77
+ selection,
78
+ timestamp
79
+ } = payload;
80
+ const participant = this.participantsState.getBySessionId(sessionId);
81
+ if (sessionId === thisSessionId ||
82
+ // Ignore old telepointer events
83
+ participant && participant.lastActive > timestamp) {
84
+ return;
85
+ }
86
+ const userId = payload.userId ? [payload.userId] : undefined;
87
+
88
+ // Set last active
89
+ this.updateLastActive(userId);
90
+ this.emitTelepointer({
91
+ type: 'telepointer',
92
+ selection,
93
+ sessionId
94
+ }, emit, 'handling participant telepointer event');
95
+ });
96
+ _defineProperty(this, "removeInactiveParticipants", (sessionId, emit) => {
97
+ clearTimeout(this.participantUpdateTimeout);
98
+ try {
99
+ this.filterInactive(sessionId, emit);
100
+ } catch (err) {
101
+ var _this$analyticsHelper3;
102
+ (_this$analyticsHelper3 = this.analyticsHelper) === null || _this$analyticsHelper3 === void 0 ? void 0 : _this$analyticsHelper3.sendErrorEvent(err, 'Failed filtering inactive participants');
103
+ }
104
+ this.participantUpdateTimeout = window.setTimeout(() => this.removeInactiveParticipants(sessionId, emit), PARTICIPANT_UPDATE_INTERVAL);
105
+ });
106
+ _defineProperty(this, "filterInactive", (sessionId, emit) => {
107
+ const now = Date.now();
108
+ const left = this.participantsState.getParticipants().filter(p => p.sessionId !== sessionId && now - p.lastActive > PARTICIPANT_UPDATE_INTERVAL);
109
+ left.forEach(p => this.participantsState.removeBySessionId(p.sessionId));
110
+ this.emitPresence({
111
+ left
112
+ }, emit, 'filtering inactive participants');
113
+ });
114
+ _defineProperty(this, "emitPresence", (data, emit, errorMessage) => {
115
+ try {
116
+ var _this$analyticsHelper4;
117
+ emit('presence', data);
118
+ (_this$analyticsHelper4 = this.analyticsHelper) === null || _this$analyticsHelper4 === void 0 ? void 0 : _this$analyticsHelper4.sendActionEvent(EVENT_ACTION.UPDATE_PARTICIPANTS, EVENT_STATUS.SUCCESS, {
119
+ participants: this.participantsState.size()
120
+ });
121
+ } catch (error) {
122
+ var _this$analyticsHelper5;
123
+ // We don't want to throw errors for Presence features as they tend to self-restore
124
+ (_this$analyticsHelper5 = this.analyticsHelper) === null || _this$analyticsHelper5 === void 0 ? void 0 : _this$analyticsHelper5.sendErrorEvent(error, `Error while ${errorMessage}`);
125
+ }
126
+ });
127
+ _defineProperty(this, "emitTelepointer", (data, emit, errorMessage) => {
128
+ try {
129
+ emit('telepointer', data);
130
+ } catch (error) {
131
+ var _this$analyticsHelper6;
132
+ // We don't want to throw errors for Presence features as they tend to self-restore
133
+ (_this$analyticsHelper6 = this.analyticsHelper) === null || _this$analyticsHelper6 === void 0 ? void 0 : _this$analyticsHelper6.sendErrorEvent(error, `Error while ${errorMessage}`);
134
+ }
135
+ });
136
+ this.participantsState = participantsState;
137
+ this.analyticsHelper = analyticsHelper;
138
+ }
139
+
140
+ /**
141
+ * Carries out 3 things: 1) enriches the participant with user data, 2) updates the participantsState, 3) emits the presence event
142
+ * @param payload Payload from incoming socket event
143
+ * @param getUser Function to get user data from confluence
144
+ * @param emit Function to execute emit from provider socket
145
+ * @returns Awaitable Promise, due to getUser
146
+ */
147
+
148
+ /**
149
+ * Called on receiving steps, emits each step's telepointer
150
+ * @param steps Steps to extract telepointers from
151
+ * @param emit Provider emit function
152
+ */
153
+ emitTelepointersFromSteps(steps, emit) {
154
+ steps.forEach(step => {
155
+ const event = telepointerFromStep(this.participantsState.getParticipants(), step);
156
+ if (event) {
157
+ this.emitTelepointer(event, emit, 'emitting telepointers from steps');
158
+ }
159
+ });
160
+ }
161
+
162
+ /**
163
+ * Called when we receive a telepointer update from another
164
+ * participant.
165
+ */
166
+ }
@@ -0,0 +1,28 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+ export class ParticipantsState {
3
+ constructor(baseParticipants = new Map()) {
4
+ _defineProperty(this, "getBySessionId", sessionId => {
5
+ const participant = this.participants.get(sessionId);
6
+ // Spread to ensure we get a deep copy
7
+ return participant ? {
8
+ ...participant
9
+ } : undefined;
10
+ });
11
+ _defineProperty(this, "setBySessionId", (sessionId, participant) => {
12
+ this.participants.set(sessionId, participant);
13
+ });
14
+ _defineProperty(this, "getParticipants", () =>
15
+ // Spread to get deep copy
16
+ [...this.participants.values()].map(p => ({
17
+ ...p
18
+ })));
19
+ _defineProperty(this, "removeBySessionId", sessionId => this.participants.delete(sessionId));
20
+ _defineProperty(this, "clear", () => this.participants.clear());
21
+ _defineProperty(this, "doesntHave", sessionId => !this.participants.has(sessionId));
22
+ _defineProperty(this, "size", () => this.participants.size);
23
+ _defineProperty(this, "updateLastActive", (now, userIds) => this.participants.forEach(p => {
24
+ p.lastActive = userIds.includes(p.userId) ? now : p.lastActive;
25
+ }));
26
+ this.participants = baseParticipants;
27
+ }
28
+ }
@@ -2,8 +2,8 @@ import { createLogger } from '../helpers/utils';
2
2
  import { ExperiencePerformanceTypes, ExperienceTypes, UFOExperience } from '@atlaskit/ufo';
3
3
  import { AcknowledgementResponseTypes } from '../types';
4
4
  const logger = createLogger('Telepointer', 'green');
5
- export const telepointersFromStep = (participants, step) => {
6
- const [participant] = Array.from(participants.values()).filter(p => p.clientId === step.clientId);
5
+ export const telepointerFromStep = (participants, step) => {
6
+ const [participant] = participants.filter(p => p.clientId === step.clientId);
7
7
  if (participant) {
8
8
  var _node$text;
9
9
  const {
@@ -1,8 +1,9 @@
1
1
  import countBy from 'lodash/countBy';
2
2
  import { ADD_STEPS_TYPE, EVENT_ACTION, EVENT_STATUS } from '../helpers/const';
3
3
  import { AcknowledgementResponseTypes } from '../types';
4
+ import { NCS_ERROR_CODE } from '../errors/error-types';
4
5
  export const commitStep = ({
5
- channel,
6
+ broadcast,
6
7
  steps,
7
8
  version,
8
9
  userId,
@@ -18,7 +19,7 @@ export const commitStep = ({
18
19
  }));
19
20
  const start = new Date().getTime();
20
21
  try {
21
- channel.broadcast('steps:commit', {
22
+ broadcast('steps:commit', {
22
23
  steps: stepsWithClientAndUserId,
23
24
  version,
24
25
  userId
@@ -35,13 +36,12 @@ export const commitStep = ({
35
36
  stepType: countBy(stepsWithClientAndUserId, stepWithClientAndUserId => stepWithClientAndUserId.stepType)
36
37
  });
37
38
  } else if (response.type === AcknowledgementResponseTypes.ERROR) {
38
- var _response$error, _response$error$data, _response$error2, _response$error2$data;
39
39
  onErrorHandled(response.error);
40
40
  analyticsHelper === null || analyticsHelper === void 0 ? void 0 : analyticsHelper.sendActionEvent(EVENT_ACTION.ADD_STEPS, EVENT_STATUS.FAILURE, {
41
41
  // User tried committing steps but they were rejected because:
42
42
  // - 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
43
43
  // - VERSION_NUMBER_ALREADY_EXISTS: while storing the steps there was a conflict meaning someone else wrote steps into the database more quickly
44
- type: ((_response$error = response.error) === null || _response$error === void 0 ? void 0 : (_response$error$data = _response$error.data) === null || _response$error$data === void 0 ? void 0 : _response$error$data.code) === 'HEAD_VERSION_UPDATE_FAILED' || ((_response$error2 = response.error) === null || _response$error2 === void 0 ? void 0 : (_response$error2$data = _response$error2.data) === null || _response$error2$data === void 0 ? void 0 : _response$error2$data.code) === 'VERSION_NUMBER_ALREADY_EXISTS' ? ADD_STEPS_TYPE.REJECTED : ADD_STEPS_TYPE.ERROR,
44
+ type: response.error.data.code === NCS_ERROR_CODE.HEAD_VERSION_UPDATE_FAILED || response.error.data.code === NCS_ERROR_CODE.VERSION_NUMBER_ALREADY_EXISTS ? ADD_STEPS_TYPE.REJECTED : ADD_STEPS_TYPE.ERROR,
45
45
  latency
46
46
  });
47
47
  analyticsHelper === null || analyticsHelper === void 0 ? void 0 : analyticsHelper.sendErrorEvent(response.error, 'Error while adding steps - Acknowledgement Error');