@webex/internal-plugin-conversation 2.59.2 → 2.59.3-next.1

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 (48) hide show
  1. package/.eslintrc.js +6 -6
  2. package/README.md +47 -47
  3. package/babel.config.js +3 -3
  4. package/dist/activities.js +4 -4
  5. package/dist/activities.js.map +1 -1
  6. package/dist/activity-thread-ordering.js +34 -34
  7. package/dist/activity-thread-ordering.js.map +1 -1
  8. package/dist/config.js +12 -12
  9. package/dist/config.js.map +1 -1
  10. package/dist/constants.js.map +1 -1
  11. package/dist/conversation.js +474 -474
  12. package/dist/conversation.js.map +1 -1
  13. package/dist/convo-error.js +4 -4
  14. package/dist/convo-error.js.map +1 -1
  15. package/dist/decryption-transforms.js +155 -155
  16. package/dist/decryption-transforms.js.map +1 -1
  17. package/dist/encryption-transforms.js.map +1 -1
  18. package/dist/index.js +2 -2
  19. package/dist/index.js.map +1 -1
  20. package/dist/share-activity.js +57 -57
  21. package/dist/share-activity.js.map +1 -1
  22. package/dist/to-array.js +7 -7
  23. package/dist/to-array.js.map +1 -1
  24. package/jest.config.js +3 -3
  25. package/package.json +21 -20
  26. package/process +1 -1
  27. package/src/activities.js +157 -157
  28. package/src/activity-thread-ordering.js +283 -283
  29. package/src/activity-threading.md +282 -282
  30. package/src/config.js +37 -37
  31. package/src/constants.js +3 -3
  32. package/src/conversation.js +2535 -2535
  33. package/src/convo-error.js +15 -15
  34. package/src/decryption-transforms.js +541 -541
  35. package/src/encryption-transforms.js +345 -345
  36. package/src/index.js +327 -327
  37. package/src/share-activity.js +436 -436
  38. package/src/to-array.js +29 -29
  39. package/test/integration/spec/create.js +290 -290
  40. package/test/integration/spec/encryption.js +333 -333
  41. package/test/integration/spec/get.js +1255 -1255
  42. package/test/integration/spec/mercury.js +94 -94
  43. package/test/integration/spec/share.js +537 -537
  44. package/test/integration/spec/verbs.js +1041 -1041
  45. package/test/unit/spec/conversation.js +823 -823
  46. package/test/unit/spec/decrypt-transforms.js +460 -460
  47. package/test/unit/spec/encryption-transforms.js +93 -93
  48. package/test/unit/spec/share-activity.js +178 -178
@@ -1,436 +1,436 @@
1
- /*!
2
- * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
3
- */
4
-
5
- import {EventEmitter} from 'events';
6
-
7
- import SCR from 'node-scr';
8
- import {proxyEvents, transferEvents} from '@webex/common';
9
- import {WebexPlugin} from '@webex/webex-core';
10
- import {filter, map, pick, some} from 'lodash';
11
- import {detectFileType, processImage} from '@webex/helper-image';
12
- import sha256 from 'crypto-js/sha256';
13
-
14
- export const EMITTER_SYMBOL = Symbol('EMITTER_SYMBOL');
15
- export const FILE_SYMBOL = Symbol('FILE_SYMBOL');
16
- const PROMISE_SYMBOL = Symbol('PROMISE_SYMBOL');
17
-
18
- /**
19
- * @class
20
- */
21
- const ShareActivity = WebexPlugin.extend({
22
- getSymbols() {
23
- return {
24
- file: FILE_SYMBOL,
25
- emitter: EMITTER_SYMBOL,
26
- };
27
- },
28
-
29
- namespace: 'Conversation',
30
-
31
- derived: {
32
- target: {
33
- deps: ['conversation'],
34
- fn() {
35
- return this.conversation;
36
- },
37
- },
38
- },
39
-
40
- session: {
41
- claimedFileType: 'string',
42
- conversation: {
43
- required: true,
44
- type: 'object',
45
- },
46
-
47
- content: 'string',
48
-
49
- clientTempId: 'string',
50
-
51
- displayName: 'string',
52
-
53
- enableThumbnails: {
54
- default: true,
55
- type: 'boolean',
56
- },
57
-
58
- hiddenSpaceUrl: 'object',
59
-
60
- mentions: 'object',
61
-
62
- spaceUrl: 'object',
63
-
64
- uploads: {
65
- type: 'object',
66
- default() {
67
- return new Map();
68
- },
69
- },
70
- },
71
-
72
- initialize(attrs, options) {
73
- Reflect.apply(WebexPlugin.prototype.initialize, this, [attrs, options]);
74
-
75
- if (attrs && attrs.conversation) {
76
- this.spaceUrl = Promise.resolve(
77
- attrs.conversation._spaceUrl ||
78
- this._retrieveSpaceUrl(`${attrs.conversation.url}/space`).then((url) => {
79
- attrs.conversation._spaceUrl = url;
80
-
81
- return url;
82
- })
83
- );
84
- this.hiddenSpaceUrl = Promise.resolve(
85
- attrs.conversation._hiddenSpaceUrl ||
86
- this._retrieveSpaceUrl(`${attrs.conversation.url}/space/hidden`).then((url) => {
87
- attrs.conversation._hiddenSpaceUrl = url;
88
-
89
- return url;
90
- })
91
- );
92
- }
93
- },
94
-
95
- /**
96
- * Adds an additional GIF to the share activity
97
- * Different from regular add to skip uploading to webex files service
98
- * @param {File} gif
99
- * @param {File} gif.image // thumbnail
100
- * @param {Object} options
101
- * @param {Object} options.actions
102
- * @returns {Promise}
103
- */
104
- addGif(gif, options) {
105
- let gifToAdd = this.uploads.get(gif);
106
-
107
- // If the gif already exists, then don't do anything
108
- if (gifToAdd) {
109
- return Promise.resolve();
110
- }
111
-
112
- gifToAdd = {
113
- displayName: gif.name,
114
- fileSize: gif.size || gif.byteLength || gif.length,
115
- mimeType: gif.type,
116
- url: 'https://giphy.com',
117
- objectType: 'file',
118
- height: gif.height,
119
- width: gif.width,
120
- image: {
121
- height: gif.image.height,
122
- width: gif.image.width,
123
- url: 'https://giphy.com',
124
- },
125
- [FILE_SYMBOL]: gif,
126
- ...pick(options, 'actions'),
127
- };
128
-
129
- this.uploads.set(gif, gifToAdd);
130
-
131
- /* Instead of encryptBinary, which produces a encrypted version of
132
- * the file for upload and a SCR (contains info needed to encrypt the
133
- * SCR itself and the displayName), we directly create an SCR.
134
- * Because we are skipping uploading, the encrypted file is not needed.
135
- */
136
- return SCR.create()
137
- .then((scr) => {
138
- scr.loc = gif.url;
139
- gifToAdd.scr = scr;
140
-
141
- return SCR.create();
142
- })
143
- .then((thumbnailScr) => {
144
- thumbnailScr.loc = gif.image.url;
145
- gifToAdd.image.scr = thumbnailScr;
146
- });
147
- },
148
-
149
- /**
150
- * Adds an additional file to the share and begins submitting it to webex
151
- * files
152
- * @param {File} file
153
- * @param {Object} options
154
- * @param {Object} options.actions
155
- * @returns {EventEmittingPromise}
156
- */
157
- add(file, options) {
158
- options = options || {};
159
- options.claimedFileType = file.name.substring(file.name.lastIndexOf('.'));
160
- let upload = this.uploads.get(file);
161
-
162
- if (upload) {
163
- return upload[PROMISE_SYMBOL];
164
- }
165
- const emitter = new EventEmitter();
166
-
167
- upload = {
168
- displayName: file.name,
169
- fileSize: file.size || file.byteLength || file.length,
170
- mimeType: file.type,
171
- objectType: 'file',
172
- [EMITTER_SYMBOL]: emitter,
173
- [FILE_SYMBOL]: file,
174
- ...pick(options, 'actions'),
175
- };
176
-
177
- this.uploads.set(file, upload);
178
- const promise = detectFileType(file, this.logger)
179
- .then((type) => {
180
- upload.mimeType = type;
181
-
182
- return processImage({
183
- file,
184
- type,
185
- thumbnailMaxWidth: this.config.thumbnailMaxWidth,
186
- thumbnailMaxHeight: this.config.thumbnailMaxHeight,
187
- enableThumbnails: this.enableThumbnails,
188
- logger: this.logger,
189
- });
190
- })
191
- .then((imageData) => {
192
- const main = this.webex.internal.encryption
193
- .encryptBinary(file)
194
- .then(({scr, cdata}) => {
195
- upload.scr = scr;
196
-
197
- return Promise.all([cdata, this.spaceUrl]);
198
- })
199
- .then(([cdata, spaceUrl]) => {
200
- const uploadPromise = this._upload(cdata, `${spaceUrl}/upload_sessions`, options);
201
-
202
- transferEvents('progress', uploadPromise, emitter);
203
-
204
- return uploadPromise;
205
- })
206
- .then((metadata) => {
207
- upload.url = upload.scr.loc = metadata.downloadUrl;
208
- });
209
-
210
- let thumb;
211
-
212
- if (imageData) {
213
- const [thumbnail, fileDimensions, thumbnailDimensions] = imageData;
214
-
215
- Object.assign(upload, fileDimensions);
216
-
217
- if (thumbnail && thumbnailDimensions) {
218
- upload.image = thumbnailDimensions;
219
- thumb = this.webex.internal.encryption
220
- .encryptBinary(thumbnail)
221
- .then(({scr, cdata}) => {
222
- upload.image.scr = scr;
223
-
224
- return Promise.all([cdata, this.hiddenSpaceUrl]);
225
- })
226
- .then(([cdata, spaceUrl]) => this._upload(cdata, `${spaceUrl}/upload_sessions`))
227
- .then((metadata) => {
228
- upload.image.url = upload.image.scr.loc = metadata.downloadUrl;
229
- });
230
- }
231
- }
232
-
233
- return Promise.all([main, thumb]);
234
- });
235
-
236
- upload[PROMISE_SYMBOL] = promise;
237
-
238
- proxyEvents(emitter, promise);
239
-
240
- return promise;
241
- },
242
-
243
- /**
244
- * Fetches the files from the share
245
- * @returns {Array}
246
- */
247
- getFiles() {
248
- const files = [];
249
-
250
- for (const [key] of this.uploads) {
251
- files.push(this.uploads.get(key)[FILE_SYMBOL]);
252
- }
253
-
254
- return files;
255
- },
256
-
257
- /**
258
- * @param {File} file
259
- * @param {string} uri
260
- * @param {Object} uploadOptions - Optional object adding additional params to request body
261
- * @private
262
- * @returns {Promise}
263
- */
264
- _upload(file, uri, uploadOptions) {
265
- const fileSize = file.length || file.size || file.byteLength;
266
- const fileHash = sha256(file).toString();
267
- const {role, claimedFileType} = uploadOptions ?? {};
268
- const initializeBody = {fileSize, claimedFileType, ...(role && {role})};
269
-
270
- return this.webex.upload({
271
- uri,
272
- file,
273
- qs: {
274
- transcode: true,
275
- },
276
- phases: {
277
- initialize: {
278
- body: initializeBody,
279
- },
280
- upload: {
281
- $url(session) {
282
- return session.uploadUrl;
283
- },
284
- },
285
- finalize: {
286
- $uri(session) {
287
- return session.finishUploadUrl;
288
- },
289
- body: {fileSize, fileHash},
290
- },
291
- },
292
- });
293
- },
294
-
295
- /**
296
- * Removes the specified file from the share (Does not currently delete the
297
- * uploaded file)
298
- * @param {File} file
299
- * @returns {Promise}
300
- */
301
- remove(file) {
302
- this.uploads.delete(file);
303
-
304
- // Returns a promise for future-proofiness.
305
- return Promise.resolve();
306
- },
307
-
308
- /**
309
- * @private
310
- * @returns {Promise<Object>}
311
- */
312
- prepare() {
313
- if (!this.uploads.size) {
314
- throw new Error('Cannot submit a share activity without at least one file');
315
- }
316
-
317
- const activity = {
318
- verb: 'share',
319
- object: {
320
- objectType: 'content',
321
- displayName: this.object && this.object.displayName ? this.object.displayName : undefined,
322
- content: this.object && this.object.content ? this.object.content : undefined,
323
- mentions: this.object && this.object.mentions ? this.object.mentions : undefined,
324
- files: {
325
- items: [],
326
- },
327
- },
328
- clientTempId: this.clientTempId,
329
- };
330
-
331
- const promises = [];
332
-
333
- this.uploads.forEach((item) => {
334
- activity.object.files.items.push(item);
335
- promises.push(item[PROMISE_SYMBOL]);
336
- });
337
-
338
- activity.object.contentCategory = this._determineContentCategory(activity.object.files.items);
339
-
340
- return Promise.all(promises).then(() => activity);
341
- },
342
-
343
- /**
344
- * @param {Array} items
345
- * @param {string} mimeType
346
- * @private
347
- * @returns {boolean}
348
- */
349
- _itemContainsActionWithMimeType(items, mimeType) {
350
- return some(items.map((item) => some(item.actions, {mimeType})));
351
- },
352
-
353
- /**
354
- * @param {Array} items
355
- * @private
356
- * @returns {string}
357
- */
358
- _determineContentCategory(items) {
359
- // determine if the items contain an image
360
- if (this._itemContainsActionWithMimeType(items, 'application/x-cisco-webex-whiteboard')) {
361
- return 'documents';
362
- }
363
-
364
- const mimeTypes = filter(map(items, 'mimeType'));
365
-
366
- if (mimeTypes.length !== items.length) {
367
- return 'documents';
368
- }
369
-
370
- const contentCategory = mimeTypes[0].split('/').shift();
371
-
372
- if (contentCategory !== 'video' && contentCategory !== 'image') {
373
- return 'documents';
374
- }
375
-
376
- for (const mimeType of mimeTypes) {
377
- if (mimeType.split('/').shift() !== contentCategory) {
378
- return 'documents';
379
- }
380
- }
381
-
382
- return `${contentCategory}s`;
383
- },
384
-
385
- /**
386
- * @param {string} uri
387
- * @returns {Promise}
388
- */
389
- _retrieveSpaceUrl(uri) {
390
- return this.webex
391
- .request({
392
- method: 'PUT',
393
- uri,
394
- })
395
- .then((res) => res.body.spaceUrl);
396
- },
397
- });
398
-
399
- /**
400
- * Instantiates a ShareActivity
401
- * @param {Object} conversation
402
- * @param {ShareActivity|Object|array} object
403
- * @param {ProxyWebex} webex
404
- * @returns {ShareActivity}
405
- */
406
- ShareActivity.create = function create(conversation, object, webex) {
407
- if (object instanceof ShareActivity) {
408
- return object;
409
- }
410
-
411
- let files;
412
-
413
- if (object?.object?.files) {
414
- files = object.object.files;
415
- Reflect.deleteProperty(object.object, 'files');
416
- }
417
-
418
- const share = new ShareActivity(
419
- {
420
- conversation,
421
- ...object,
422
- },
423
- {
424
- parent: webex,
425
- }
426
- );
427
-
428
- files = files?.items ?? files;
429
- if (files) {
430
- files.forEach((file) => share.add(file));
431
- }
432
-
433
- return share;
434
- };
435
-
436
- export default ShareActivity;
1
+ /*!
2
+ * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
3
+ */
4
+
5
+ import {EventEmitter} from 'events';
6
+
7
+ import SCR from 'node-scr';
8
+ import {proxyEvents, transferEvents} from '@webex/common';
9
+ import {WebexPlugin} from '@webex/webex-core';
10
+ import {filter, map, pick, some} from 'lodash';
11
+ import {detectFileType, processImage} from '@webex/helper-image';
12
+ import sha256 from 'crypto-js/sha256';
13
+
14
+ export const EMITTER_SYMBOL = Symbol('EMITTER_SYMBOL');
15
+ export const FILE_SYMBOL = Symbol('FILE_SYMBOL');
16
+ const PROMISE_SYMBOL = Symbol('PROMISE_SYMBOL');
17
+
18
+ /**
19
+ * @class
20
+ */
21
+ const ShareActivity = WebexPlugin.extend({
22
+ getSymbols() {
23
+ return {
24
+ file: FILE_SYMBOL,
25
+ emitter: EMITTER_SYMBOL,
26
+ };
27
+ },
28
+
29
+ namespace: 'Conversation',
30
+
31
+ derived: {
32
+ target: {
33
+ deps: ['conversation'],
34
+ fn() {
35
+ return this.conversation;
36
+ },
37
+ },
38
+ },
39
+
40
+ session: {
41
+ claimedFileType: 'string',
42
+ conversation: {
43
+ required: true,
44
+ type: 'object',
45
+ },
46
+
47
+ content: 'string',
48
+
49
+ clientTempId: 'string',
50
+
51
+ displayName: 'string',
52
+
53
+ enableThumbnails: {
54
+ default: true,
55
+ type: 'boolean',
56
+ },
57
+
58
+ hiddenSpaceUrl: 'object',
59
+
60
+ mentions: 'object',
61
+
62
+ spaceUrl: 'object',
63
+
64
+ uploads: {
65
+ type: 'object',
66
+ default() {
67
+ return new Map();
68
+ },
69
+ },
70
+ },
71
+
72
+ initialize(attrs, options) {
73
+ Reflect.apply(WebexPlugin.prototype.initialize, this, [attrs, options]);
74
+
75
+ if (attrs && attrs.conversation) {
76
+ this.spaceUrl = Promise.resolve(
77
+ attrs.conversation._spaceUrl ||
78
+ this._retrieveSpaceUrl(`${attrs.conversation.url}/space`).then((url) => {
79
+ attrs.conversation._spaceUrl = url;
80
+
81
+ return url;
82
+ })
83
+ );
84
+ this.hiddenSpaceUrl = Promise.resolve(
85
+ attrs.conversation._hiddenSpaceUrl ||
86
+ this._retrieveSpaceUrl(`${attrs.conversation.url}/space/hidden`).then((url) => {
87
+ attrs.conversation._hiddenSpaceUrl = url;
88
+
89
+ return url;
90
+ })
91
+ );
92
+ }
93
+ },
94
+
95
+ /**
96
+ * Adds an additional GIF to the share activity
97
+ * Different from regular add to skip uploading to webex files service
98
+ * @param {File} gif
99
+ * @param {File} gif.image // thumbnail
100
+ * @param {Object} options
101
+ * @param {Object} options.actions
102
+ * @returns {Promise}
103
+ */
104
+ addGif(gif, options) {
105
+ let gifToAdd = this.uploads.get(gif);
106
+
107
+ // If the gif already exists, then don't do anything
108
+ if (gifToAdd) {
109
+ return Promise.resolve();
110
+ }
111
+
112
+ gifToAdd = {
113
+ displayName: gif.name,
114
+ fileSize: gif.size || gif.byteLength || gif.length,
115
+ mimeType: gif.type,
116
+ url: 'https://giphy.com',
117
+ objectType: 'file',
118
+ height: gif.height,
119
+ width: gif.width,
120
+ image: {
121
+ height: gif.image.height,
122
+ width: gif.image.width,
123
+ url: 'https://giphy.com',
124
+ },
125
+ [FILE_SYMBOL]: gif,
126
+ ...pick(options, 'actions'),
127
+ };
128
+
129
+ this.uploads.set(gif, gifToAdd);
130
+
131
+ /* Instead of encryptBinary, which produces a encrypted version of
132
+ * the file for upload and a SCR (contains info needed to encrypt the
133
+ * SCR itself and the displayName), we directly create an SCR.
134
+ * Because we are skipping uploading, the encrypted file is not needed.
135
+ */
136
+ return SCR.create()
137
+ .then((scr) => {
138
+ scr.loc = gif.url;
139
+ gifToAdd.scr = scr;
140
+
141
+ return SCR.create();
142
+ })
143
+ .then((thumbnailScr) => {
144
+ thumbnailScr.loc = gif.image.url;
145
+ gifToAdd.image.scr = thumbnailScr;
146
+ });
147
+ },
148
+
149
+ /**
150
+ * Adds an additional file to the share and begins submitting it to webex
151
+ * files
152
+ * @param {File} file
153
+ * @param {Object} options
154
+ * @param {Object} options.actions
155
+ * @returns {EventEmittingPromise}
156
+ */
157
+ add(file, options) {
158
+ options = options || {};
159
+ options.claimedFileType = file.name.substring(file.name.lastIndexOf('.'));
160
+ let upload = this.uploads.get(file);
161
+
162
+ if (upload) {
163
+ return upload[PROMISE_SYMBOL];
164
+ }
165
+ const emitter = new EventEmitter();
166
+
167
+ upload = {
168
+ displayName: file.name,
169
+ fileSize: file.size || file.byteLength || file.length,
170
+ mimeType: file.type,
171
+ objectType: 'file',
172
+ [EMITTER_SYMBOL]: emitter,
173
+ [FILE_SYMBOL]: file,
174
+ ...pick(options, 'actions'),
175
+ };
176
+
177
+ this.uploads.set(file, upload);
178
+ const promise = detectFileType(file, this.logger)
179
+ .then((type) => {
180
+ upload.mimeType = type;
181
+
182
+ return processImage({
183
+ file,
184
+ type,
185
+ thumbnailMaxWidth: this.config.thumbnailMaxWidth,
186
+ thumbnailMaxHeight: this.config.thumbnailMaxHeight,
187
+ enableThumbnails: this.enableThumbnails,
188
+ logger: this.logger,
189
+ });
190
+ })
191
+ .then((imageData) => {
192
+ const main = this.webex.internal.encryption
193
+ .encryptBinary(file)
194
+ .then(({scr, cdata}) => {
195
+ upload.scr = scr;
196
+
197
+ return Promise.all([cdata, this.spaceUrl]);
198
+ })
199
+ .then(([cdata, spaceUrl]) => {
200
+ const uploadPromise = this._upload(cdata, `${spaceUrl}/upload_sessions`, options);
201
+
202
+ transferEvents('progress', uploadPromise, emitter);
203
+
204
+ return uploadPromise;
205
+ })
206
+ .then((metadata) => {
207
+ upload.url = upload.scr.loc = metadata.downloadUrl;
208
+ });
209
+
210
+ let thumb;
211
+
212
+ if (imageData) {
213
+ const [thumbnail, fileDimensions, thumbnailDimensions] = imageData;
214
+
215
+ Object.assign(upload, fileDimensions);
216
+
217
+ if (thumbnail && thumbnailDimensions) {
218
+ upload.image = thumbnailDimensions;
219
+ thumb = this.webex.internal.encryption
220
+ .encryptBinary(thumbnail)
221
+ .then(({scr, cdata}) => {
222
+ upload.image.scr = scr;
223
+
224
+ return Promise.all([cdata, this.hiddenSpaceUrl]);
225
+ })
226
+ .then(([cdata, spaceUrl]) => this._upload(cdata, `${spaceUrl}/upload_sessions`))
227
+ .then((metadata) => {
228
+ upload.image.url = upload.image.scr.loc = metadata.downloadUrl;
229
+ });
230
+ }
231
+ }
232
+
233
+ return Promise.all([main, thumb]);
234
+ });
235
+
236
+ upload[PROMISE_SYMBOL] = promise;
237
+
238
+ proxyEvents(emitter, promise);
239
+
240
+ return promise;
241
+ },
242
+
243
+ /**
244
+ * Fetches the files from the share
245
+ * @returns {Array}
246
+ */
247
+ getFiles() {
248
+ const files = [];
249
+
250
+ for (const [key] of this.uploads) {
251
+ files.push(this.uploads.get(key)[FILE_SYMBOL]);
252
+ }
253
+
254
+ return files;
255
+ },
256
+
257
+ /**
258
+ * @param {File} file
259
+ * @param {string} uri
260
+ * @param {Object} uploadOptions - Optional object adding additional params to request body
261
+ * @private
262
+ * @returns {Promise}
263
+ */
264
+ _upload(file, uri, uploadOptions) {
265
+ const fileSize = file.length || file.size || file.byteLength;
266
+ const fileHash = sha256(file).toString();
267
+ const {role, claimedFileType} = uploadOptions ?? {};
268
+ const initializeBody = {fileSize, claimedFileType, ...(role && {role})};
269
+
270
+ return this.webex.upload({
271
+ uri,
272
+ file,
273
+ qs: {
274
+ transcode: true,
275
+ },
276
+ phases: {
277
+ initialize: {
278
+ body: initializeBody,
279
+ },
280
+ upload: {
281
+ $url(session) {
282
+ return session.uploadUrl;
283
+ },
284
+ },
285
+ finalize: {
286
+ $uri(session) {
287
+ return session.finishUploadUrl;
288
+ },
289
+ body: {fileSize, fileHash},
290
+ },
291
+ },
292
+ });
293
+ },
294
+
295
+ /**
296
+ * Removes the specified file from the share (Does not currently delete the
297
+ * uploaded file)
298
+ * @param {File} file
299
+ * @returns {Promise}
300
+ */
301
+ remove(file) {
302
+ this.uploads.delete(file);
303
+
304
+ // Returns a promise for future-proofiness.
305
+ return Promise.resolve();
306
+ },
307
+
308
+ /**
309
+ * @private
310
+ * @returns {Promise<Object>}
311
+ */
312
+ prepare() {
313
+ if (!this.uploads.size) {
314
+ throw new Error('Cannot submit a share activity without at least one file');
315
+ }
316
+
317
+ const activity = {
318
+ verb: 'share',
319
+ object: {
320
+ objectType: 'content',
321
+ displayName: this.object && this.object.displayName ? this.object.displayName : undefined,
322
+ content: this.object && this.object.content ? this.object.content : undefined,
323
+ mentions: this.object && this.object.mentions ? this.object.mentions : undefined,
324
+ files: {
325
+ items: [],
326
+ },
327
+ },
328
+ clientTempId: this.clientTempId,
329
+ };
330
+
331
+ const promises = [];
332
+
333
+ this.uploads.forEach((item) => {
334
+ activity.object.files.items.push(item);
335
+ promises.push(item[PROMISE_SYMBOL]);
336
+ });
337
+
338
+ activity.object.contentCategory = this._determineContentCategory(activity.object.files.items);
339
+
340
+ return Promise.all(promises).then(() => activity);
341
+ },
342
+
343
+ /**
344
+ * @param {Array} items
345
+ * @param {string} mimeType
346
+ * @private
347
+ * @returns {boolean}
348
+ */
349
+ _itemContainsActionWithMimeType(items, mimeType) {
350
+ return some(items.map((item) => some(item.actions, {mimeType})));
351
+ },
352
+
353
+ /**
354
+ * @param {Array} items
355
+ * @private
356
+ * @returns {string}
357
+ */
358
+ _determineContentCategory(items) {
359
+ // determine if the items contain an image
360
+ if (this._itemContainsActionWithMimeType(items, 'application/x-cisco-webex-whiteboard')) {
361
+ return 'documents';
362
+ }
363
+
364
+ const mimeTypes = filter(map(items, 'mimeType'));
365
+
366
+ if (mimeTypes.length !== items.length) {
367
+ return 'documents';
368
+ }
369
+
370
+ const contentCategory = mimeTypes[0].split('/').shift();
371
+
372
+ if (contentCategory !== 'video' && contentCategory !== 'image') {
373
+ return 'documents';
374
+ }
375
+
376
+ for (const mimeType of mimeTypes) {
377
+ if (mimeType.split('/').shift() !== contentCategory) {
378
+ return 'documents';
379
+ }
380
+ }
381
+
382
+ return `${contentCategory}s`;
383
+ },
384
+
385
+ /**
386
+ * @param {string} uri
387
+ * @returns {Promise}
388
+ */
389
+ _retrieveSpaceUrl(uri) {
390
+ return this.webex
391
+ .request({
392
+ method: 'PUT',
393
+ uri,
394
+ })
395
+ .then((res) => res.body.spaceUrl);
396
+ },
397
+ });
398
+
399
+ /**
400
+ * Instantiates a ShareActivity
401
+ * @param {Object} conversation
402
+ * @param {ShareActivity|Object|array} object
403
+ * @param {ProxyWebex} webex
404
+ * @returns {ShareActivity}
405
+ */
406
+ ShareActivity.create = function create(conversation, object, webex) {
407
+ if (object instanceof ShareActivity) {
408
+ return object;
409
+ }
410
+
411
+ let files;
412
+
413
+ if (object?.object?.files) {
414
+ files = object.object.files;
415
+ Reflect.deleteProperty(object.object, 'files');
416
+ }
417
+
418
+ const share = new ShareActivity(
419
+ {
420
+ conversation,
421
+ ...object,
422
+ },
423
+ {
424
+ parent: webex,
425
+ }
426
+ );
427
+
428
+ files = files?.items ?? files;
429
+ if (files) {
430
+ files.forEach((file) => share.add(file));
431
+ }
432
+
433
+ return share;
434
+ };
435
+
436
+ export default ShareActivity;