@webex/internal-plugin-conversation 2.59.1 → 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,537 +1,537 @@
1
- /*!
2
- * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
3
- */
4
-
5
- import '@webex/internal-plugin-conversation';
6
-
7
- import {Defer} from '@webex/common';
8
- import WebexCore from '@webex/webex-core';
9
- import fh from '@webex/test-helper-file';
10
- import sinon from 'sinon';
11
- import {assert} from '@webex/test-helper-chai';
12
- import testUsers from '@webex/test-helper-test-users';
13
- import {find} from 'lodash';
14
- import uuid from 'uuid';
15
- import {flaky, skipInNode, browserOnly} from '@webex/test-helper-mocha';
16
-
17
- /**
18
- * Resolves with the first argument passed in, after applying `fn()` on that
19
- * argument
20
- * @param {Function} fn
21
- * @returns {Promise<mixed>}
22
- */
23
- function returnFirstArg(fn) {
24
- return (result) => Promise.resolve(fn(result)).then(() => result);
25
- }
26
-
27
- describe('plugin-conversation', function () {
28
- this.timeout(120000);
29
- describe('share', () => {
30
- let mccoy, participants, webex, spock;
31
-
32
- before(() =>
33
- testUsers.create({count: 3}).then(async (users) => {
34
- participants = users;
35
- [spock, mccoy] = participants;
36
-
37
- // Pause for 5 seconds for CI
38
- await new Promise((done) => setTimeout(done, 5000));
39
-
40
- webex = new WebexCore({
41
- credentials: {
42
- authorization: spock.token,
43
- },
44
- });
45
-
46
- mccoy.webex = new WebexCore({
47
- credentials: {
48
- authorization: mccoy.token,
49
- },
50
- });
51
-
52
- return Promise.all([
53
- webex.internal.mercury.connect(),
54
- mccoy.webex.internal.mercury.connect(),
55
- ]);
56
- })
57
- );
58
-
59
- after(() =>
60
- Promise.all([
61
- webex && webex.internal.mercury.disconnect(),
62
- mccoy && mccoy.webex.internal.mercury.disconnect(),
63
- ])
64
- );
65
-
66
- let conversation;
67
-
68
- beforeEach(() => {
69
- if (conversation) {
70
- return Promise.resolve();
71
- }
72
-
73
- return webex.internal.conversation.create({participants}).then((c) => {
74
- conversation = c;
75
- });
76
- });
77
-
78
- let hashTestText = '#test.txt';
79
- let sampleImageSmallOnePng = 'sample-image-small-one.png';
80
- let sampleImageSmallTwoPng = 'sample-image-small-two.png';
81
- let sampleImageLargeJpg = 'sample-image-large.jpg';
82
- let sampleImageLargeNoEXIFJpg = 'sample-image-large-no-exif.jpg';
83
- let samplePowerpointTwoPagePpt = 'sample-powerpoint-two-page.ppt';
84
- let sampleTextOne = 'sample-text-one.txt';
85
- let sampleTextTwo = 'sample-text-two.txt';
86
- const sampleGif = 'sample-gif.gif';
87
-
88
- before(() =>
89
- Promise.all([
90
- fh.fetchWithoutMagic(hashTestText),
91
- fh.fetchWithoutMagic(sampleImageSmallOnePng),
92
- fh.fetchWithoutMagic(sampleImageSmallTwoPng),
93
- fh.fetchWithoutMagic(sampleImageLargeJpg),
94
- fh.fetchWithoutMagic(sampleImageLargeNoEXIFJpg),
95
- fh.fetchWithoutMagic(samplePowerpointTwoPagePpt),
96
- fh.fetchWithoutMagic(sampleTextOne),
97
- fh.fetchWithoutMagic(sampleTextTwo),
98
- ]).then((res) => {
99
- [
100
- hashTestText,
101
- sampleImageSmallOnePng,
102
- sampleImageSmallTwoPng,
103
- sampleImageLargeJpg,
104
- sampleImageLargeNoEXIFJpg,
105
- samplePowerpointTwoPagePpt,
106
- sampleTextOne,
107
- sampleTextTwo,
108
- ] = res;
109
- })
110
- );
111
-
112
- describe('#share()', () => {
113
- it('shares the specified file to the specified conversation', () =>
114
- webex.internal.conversation
115
- .share(conversation, [sampleTextOne])
116
- .then((activity) => {
117
- assert.isActivity(activity);
118
- assert.isEncryptedActivity(activity);
119
- assert.isFileItem(activity.object.files.items[0]);
120
-
121
- return webex.internal.conversation.download(activity.object.files.items[0]);
122
- })
123
- .then(returnFirstArg((f) => assert.match(f.type, /text\/plain/)))
124
- .then((f) =>
125
- fh.isMatchingFile(f, sampleTextOne).then((result) => assert.isTrue(result))
126
- ));
127
-
128
- it('shares the specified set of files to the specified conversation', () =>
129
- webex.internal.conversation
130
- .share(conversation, [sampleTextOne, sampleTextTwo])
131
- .then((activity) => {
132
- assert.isActivity(activity);
133
- assert.isEncryptedActivity(activity);
134
- assert.isFileItem(activity.object.files.items[0]);
135
- assert.isFileItem(activity.object.files.items[1]);
136
-
137
- return Promise.all([
138
- webex.internal.conversation
139
- .download(activity.object.files.items[0])
140
- .then(returnFirstArg((f) => assert.match(f.type, /text\/plain/))),
141
- webex.internal.conversation
142
- .download(activity.object.files.items[1])
143
- .then(returnFirstArg((f) => assert.match(f.type, /text\/plain/))),
144
- ]);
145
- })
146
- .then(([file0, file1]) =>
147
- Promise.all([
148
- fh.isMatchingFile(file0, sampleTextOne).then((result) => assert.isTrue(result)),
149
- fh.isMatchingFile(file1, sampleTextTwo).then((result) => assert.isTrue(result)),
150
- ])
151
- ));
152
-
153
- describe('files with special characters', () => {
154
- it('shares the specified file to the specified conversation', () =>
155
- webex.internal.conversation
156
- .share(conversation, [hashTestText])
157
- .then((activity) => {
158
- assert.isActivity(activity);
159
- assert.isEncryptedActivity(activity);
160
- assert.isFileItem(activity.object.files.items[0]);
161
-
162
- return webex.internal.conversation.download(activity.object.files.items[0]);
163
- })
164
- // in node, this'll be 'text/plain', in a browser, it'll be
165
- // 'text/html'. I'm pretty sure it's caused by the # convincing
166
- // express it's a hashroute and treating it as html. The discrepancy
167
- // has no bearing on the test's validity. Further, we need to use
168
- // match rather than equal because some browser append the charset.
169
- .then(returnFirstArg((f) => assert.match(f.type, hashTestText.type || /text\/plain/)))
170
- .then((f) =>
171
- fh.isMatchingFile(f, hashTestText).then((result) => assert.isTrue(result))
172
- ));
173
- });
174
-
175
- it('shares an image with no EXIF data to the specified conversation and correctly error handles', () =>
176
- webex.internal.conversation
177
- .share(conversation, [sampleImageLargeNoEXIFJpg])
178
- .then((activity) => {
179
- assert.isActivity(activity);
180
- assert.isEncryptedActivity(activity);
181
-
182
- const fileItem = activity.object.files.items[0];
183
-
184
- assert.isFileItem(fileItem);
185
-
186
- const thumbnailItem = activity.object.files.items[0].image;
187
-
188
- assert.isThumbnailItem(thumbnailItem);
189
- assert.equal(thumbnailItem.width, 640);
190
- assert.isAbove(thumbnailItem.height, 358);
191
- assert.isBelow(thumbnailItem.height, 361);
192
-
193
- return webex.internal.conversation.download(activity.object.files.items[0]);
194
- })
195
- .then(returnFirstArg((f) => assert.equal(f.type, 'image/jpeg')))
196
- .then((f) =>
197
- fh.isMatchingFile(f, sampleImageLargeNoEXIFJpg).then((result) => assert.isTrue(result))
198
- ));
199
-
200
- it('shares the specified image to the specified conversation', () =>
201
- webex.internal.conversation
202
- .share(conversation, [sampleImageLargeJpg])
203
- .then((activity) => {
204
- assert.isActivity(activity);
205
- assert.isEncryptedActivity(activity);
206
-
207
- const fileItem = activity.object.files.items[0];
208
-
209
- assert.isFileItem(fileItem);
210
-
211
- const thumbnailItem = activity.object.files.items[0].image;
212
-
213
- assert.isThumbnailItem(thumbnailItem);
214
- assert.equal(thumbnailItem.width, 640);
215
- assert.isAbove(thumbnailItem.height, 330);
216
- assert.isBelow(thumbnailItem.height, 361);
217
-
218
- return webex.internal.conversation.download(activity.object.files.items[0]);
219
- })
220
- .then(returnFirstArg((f) => assert.equal(f.type, 'image/jpeg')))
221
- .then((f) =>
222
- fh.isMatchingFile(f, sampleImageLargeJpg).then((result) => assert.isTrue(result))
223
- ));
224
-
225
- it('shares the specified set of images the specified conversation', () =>
226
- webex.internal.conversation
227
- .share(conversation, [sampleImageSmallOnePng, sampleImageSmallTwoPng])
228
- .then((activity) => {
229
- assert.isActivity(activity);
230
- assert.isEncryptedActivity(activity);
231
- assert.isFileItem(activity.object.files.items[0]);
232
- assert.isFileItem(activity.object.files.items[1]);
233
- assert.isThumbnailItem(activity.object.files.items[0].image);
234
- assert.isThumbnailItem(activity.object.files.items[1].image);
235
-
236
- return Promise.all([
237
- webex.internal.conversation
238
- .download(activity.object.files.items[0])
239
- .then(returnFirstArg((f) => assert.equal(f.type, 'image/png'))),
240
- webex.internal.conversation
241
- .download(activity.object.files.items[1])
242
- .then(returnFirstArg((f) => assert.equal(f.type, 'image/png'))),
243
- ]);
244
- })
245
- .then(([file0, file1]) =>
246
- Promise.all([
247
- fh
248
- .isMatchingFile(file0, sampleImageSmallOnePng)
249
- .then((result) => assert.isTrue(result)),
250
- fh
251
- .isMatchingFile(file1, sampleImageSmallTwoPng)
252
- .then((result) => assert.isTrue(result)),
253
- ])
254
- ));
255
-
256
- describe('when it shares a transcodable file', () => {
257
- let activities;
258
- let blockUntilTranscode;
259
- let clientTempId;
260
- let objectUrl;
261
-
262
- beforeEach(() => {
263
- clientTempId = uuid.v4();
264
- activities = [];
265
- webex.internal.mercury.on('event:conversation.activity', onMessage);
266
- blockUntilTranscode = new Defer();
267
- });
268
-
269
- afterEach(
270
- () => webex && webex.internal.mercury.off('event:conversation.activity', onMessage)
271
- );
272
-
273
- function onMessage(message) {
274
- activities.push(message.data.activity);
275
-
276
- if (message.data.activity.clientTempId === clientTempId) {
277
- objectUrl = message.data.activity.object.url;
278
- }
279
-
280
- if (objectUrl) {
281
- const updateActivity = find(
282
- activities,
283
- (activity) => activity.verb === 'update' && activity.object.url === objectUrl
284
- );
285
-
286
- if (updateActivity) {
287
- blockUntilTranscode.resolve(updateActivity);
288
- }
289
- }
290
- }
291
-
292
- // doesn't seem like we get mercury event back to update transcoded file in time
293
- // https://jira-eng-gpk2.cisco.com/jira/browse/SPARK-166178
294
- it.skip('mercury receives an update', () =>
295
- webex.internal.conversation
296
- .share(conversation, {
297
- object: {
298
- files: [samplePowerpointTwoPagePpt],
299
- },
300
- clientTempId,
301
- })
302
- .then((activity) => {
303
- assert.equal(activity.clientTempId, clientTempId);
304
- activities.push(activity);
305
-
306
- return webex.internal.conversation
307
- .download(activity.object.files.items[0])
308
- .then((f) => assert.equal(f.type, 'application/vnd.ms-powerpoint'))
309
- .then(() => blockUntilTranscode.promise)
310
- .then((updateActivity) => {
311
- assert.equal(updateActivity.object.url, activity.object.url);
312
- assert.lengthOf(
313
- updateActivity.object.files.items[0].transcodedCollection.items[0].files.items,
314
- 2
315
- );
316
- // Prove that the newly transcoded file can be downloaded and
317
- // decrypted
318
- const firstItem =
319
- updateActivity.object.files.items[0].transcodedCollection.items[0].files
320
- .items[0];
321
-
322
- return webex.internal.conversation.download(firstItem);
323
- });
324
- }));
325
- });
326
-
327
- it('shares a whiteboard', () => {
328
- const activity = webex.internal.conversation.makeShare(conversation);
329
-
330
- activity.add(sampleImageSmallOnePng, {
331
- actions: [
332
- {
333
- type: 'edit',
334
- mimeType: 'application/x-cisco-webex-whiteboard',
335
- url: 'https://boards.example.com/boards/1',
336
- },
337
- ],
338
- });
339
-
340
- return webex.internal.conversation
341
- .share(conversation, activity)
342
- .then((share) => {
343
- assert.isActivity(share);
344
- assert.isEncryptedActivity(share);
345
- assert.isFileItem(share.object.files.items[0]);
346
- assert.isThumbnailItem(share.object.files.items[0].image);
347
- assert.equal(share.object.contentCategory, 'documents');
348
- assert.isArray(share.object.files.items[0].actions);
349
- assert.equal(share.object.files.items[0].actions[0].type, 'edit');
350
- assert.equal(
351
- share.object.files.items[0].actions[0].mimeType,
352
- 'application/x-cisco-webex-whiteboard'
353
- );
354
- assert.equal(
355
- share.object.files.items[0].actions[0].url,
356
- 'https://boards.example.com/boards/1'
357
- );
358
-
359
- return webex.internal.conversation
360
- .download(share.object.files.items[0])
361
- .then(returnFirstArg((f) => assert.equal(f.type, 'image/png')));
362
- })
363
- .then((file0) =>
364
- fh.isMatchingFile(file0, sampleImageSmallOnePng).then((result) => assert.isTrue(result))
365
- );
366
- });
367
- });
368
-
369
- describe('#makeShare', () => {
370
- // http-core doesn't current do upload progress events in node, so this
371
- // test is browser-only for now
372
- skipInNode(flaky(it, process.env.SKIP_FLAKY_TESTS))(
373
- 'provides an interface for file upload events',
374
- () => {
375
- const spy = sinon.spy();
376
- const share = webex.internal.conversation.makeShare(conversation);
377
- const emitter = share.add(sampleImageSmallOnePng);
378
-
379
- emitter.on('progress', spy);
380
-
381
- return webex.internal.conversation
382
- .share(conversation, share)
383
- .then(() => assert.called(spy));
384
- }
385
- );
386
-
387
- it('shares a file with a name', () => {
388
- const share = webex.internal.conversation.makeShare(conversation);
389
-
390
- share.add(sampleImageSmallOnePng);
391
- share.object = {
392
- displayName: 'a name',
393
- };
394
-
395
- return webex.internal.conversation
396
- .share(conversation, share)
397
- .then((activity) => {
398
- assert.equal(activity.object.displayName, 'a name');
399
-
400
- return webex.internal.conversation.download(activity.object.files.items[0]);
401
- })
402
- .then(returnFirstArg((f) => assert.equal(f.type, 'image/png')))
403
- .then((file) => fh.isMatchingFile(file, sampleImageSmallOnePng));
404
- });
405
-
406
- it('allows removal of a file from the share', () => {
407
- const share = webex.internal.conversation.makeShare(conversation);
408
-
409
- share.add(sampleImageSmallOnePng);
410
- share.add(sampleImageSmallTwoPng);
411
- share.remove(sampleImageSmallOnePng);
412
- share.object = {
413
- displayName: 'a name',
414
- };
415
-
416
- return webex.internal.conversation
417
- .share(conversation, share)
418
- .then((activity) => {
419
- assert.equal(activity.object.displayName, 'a name');
420
- assert.lengthOf(activity.object.files.items, 1);
421
-
422
- return webex.internal.conversation.download(activity.object.files.items[0]);
423
- })
424
- .then(returnFirstArg((f) => assert.equal(f.type, 'image/png')))
425
- .then((file) => fh.isMatchingFile(file, sampleImageSmallTwoPng));
426
- });
427
-
428
- it('shares a file to a thread', () => {
429
- const share = webex.internal.conversation.makeShare(conversation);
430
-
431
- share.add(sampleImageSmallOnePng);
432
- share.object = {
433
- displayName: 'a name',
434
- };
435
-
436
- let parentActivityId;
437
-
438
- return webex.internal.conversation
439
- .share(conversation, share)
440
- .then((activity) => {
441
- assert.equal(activity.object.displayName, 'a name');
442
- const threadShare = webex.internal.conversation.makeShare(conversation);
443
-
444
- threadShare.add(sampleImageSmallOnePng);
445
- threadShare.object = {
446
- displayName: 'a thread share name',
447
- };
448
- threadShare.activityType = 'reply';
449
- threadShare.parentActivityId = activity.id;
450
- parentActivityId = activity.id;
451
-
452
- return webex.internal.conversation.share(conversation, threadShare);
453
- })
454
- .then((activity) => {
455
- const {id, type} = activity.parent;
456
-
457
- assert.equal(id, parentActivityId);
458
- assert.equal(type, 'reply');
459
-
460
- return webex.internal.conversation.download(activity.object.files.items[0]);
461
- })
462
- .then(returnFirstArg((f) => assert.equal(f.type, 'image/png')))
463
- .then((file) => fh.isMatchingFile(file, sampleImageSmallOnePng));
464
- });
465
- });
466
-
467
- describe('#addGif', () => {
468
- let blob, buffer;
469
-
470
- // Read file as buffer
471
- browserOnly(before)(() =>
472
- fh.fetch(sampleGif).then((file) => {
473
- blob = file;
474
-
475
- return new Promise((resolve) => {
476
- /* global FileReader */
477
- const fileReader = new FileReader();
478
-
479
- fileReader.onload = function () {
480
- buffer = this.result;
481
- resolve();
482
- };
483
- fileReader.readAsArrayBuffer(blob);
484
- });
485
- })
486
- );
487
-
488
- browserOnly(it)(
489
- 'if the giphy does not exist, then we check it gets added to this.uploads',
490
- (done) => {
491
- // eslint-disable-next-line no-undef
492
- const file = new File([buffer], blob.name, {type: 'image/gif'});
493
-
494
- const originalGiphyURL = 'https://media1.giphy.com/media/nXxOjZrbnbRxS/giphy.gif';
495
- const originalGiphyStillURL = 'https://media1.giphy.com/media/nXxOjZrbnbRxS/giphy_s.gif';
496
- const url = 'https://giphy.com';
497
-
498
- // simulate in web client where
499
- Object.defineProperty(file, 'url', {value: originalGiphyURL});
500
- // define thumbnail
501
- Object.defineProperty(file, 'image', {
502
- value: {
503
- height: file.width,
504
- width: file.height,
505
- url: originalGiphyStillURL,
506
- },
507
- });
508
-
509
- const share = webex.internal.conversation.makeShare(conversation);
510
-
511
- // Check that initially there were no uploads
512
- assert.isTrue(share.uploads.size === 0);
513
- share.addGif(file).then(() => {
514
- assert.equal(share.uploads.size, 1);
515
- assert.equal(share.uploads.get(file).objectType, 'file');
516
- assert.equal(share.uploads.get(file).displayName, sampleGif);
517
- assert.equal(share.uploads.get(file).mimeType, 'image/gif');
518
- assert.equal(share.uploads.get(file).fileSize, 473119);
519
- assert.equal(share.uploads.get(file).width, 200);
520
- assert.equal(share.uploads.get(file).height, 270);
521
- assert.equal(share.uploads.get(file).url, url);
522
- assert.exists(share.uploads.get(file).scr);
523
- assert.equal(share.uploads.get(file).scr.loc, originalGiphyURL);
524
-
525
- assert.exists(share.uploads.get(file).image);
526
- assert.equal(share.uploads.get(file).image.width, 200);
527
- assert.equal(share.uploads.get(file).image.height, 270);
528
- assert.equal(share.uploads.get(file).image.url, url);
529
- assert.exists(share.uploads.get(file).image.scr);
530
- assert.equal(share.uploads.get(file).image.scr.loc, originalGiphyStillURL);
531
- });
532
- done();
533
- }
534
- );
535
- });
536
- });
537
- });
1
+ /*!
2
+ * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
3
+ */
4
+
5
+ import '@webex/internal-plugin-conversation';
6
+
7
+ import {Defer} from '@webex/common';
8
+ import WebexCore from '@webex/webex-core';
9
+ import fh from '@webex/test-helper-file';
10
+ import sinon from 'sinon';
11
+ import {assert} from '@webex/test-helper-chai';
12
+ import testUsers from '@webex/test-helper-test-users';
13
+ import {find} from 'lodash';
14
+ import uuid from 'uuid';
15
+ import {flaky, skipInNode, browserOnly} from '@webex/test-helper-mocha';
16
+
17
+ /**
18
+ * Resolves with the first argument passed in, after applying `fn()` on that
19
+ * argument
20
+ * @param {Function} fn
21
+ * @returns {Promise<mixed>}
22
+ */
23
+ function returnFirstArg(fn) {
24
+ return (result) => Promise.resolve(fn(result)).then(() => result);
25
+ }
26
+
27
+ describe('plugin-conversation', function () {
28
+ this.timeout(120000);
29
+ describe('share', () => {
30
+ let mccoy, participants, webex, spock;
31
+
32
+ before(() =>
33
+ testUsers.create({count: 3}).then(async (users) => {
34
+ participants = users;
35
+ [spock, mccoy] = participants;
36
+
37
+ // Pause for 5 seconds for CI
38
+ await new Promise((done) => setTimeout(done, 5000));
39
+
40
+ webex = new WebexCore({
41
+ credentials: {
42
+ authorization: spock.token,
43
+ },
44
+ });
45
+
46
+ mccoy.webex = new WebexCore({
47
+ credentials: {
48
+ authorization: mccoy.token,
49
+ },
50
+ });
51
+
52
+ return Promise.all([
53
+ webex.internal.mercury.connect(),
54
+ mccoy.webex.internal.mercury.connect(),
55
+ ]);
56
+ })
57
+ );
58
+
59
+ after(() =>
60
+ Promise.all([
61
+ webex && webex.internal.mercury.disconnect(),
62
+ mccoy && mccoy.webex.internal.mercury.disconnect(),
63
+ ])
64
+ );
65
+
66
+ let conversation;
67
+
68
+ beforeEach(() => {
69
+ if (conversation) {
70
+ return Promise.resolve();
71
+ }
72
+
73
+ return webex.internal.conversation.create({participants}).then((c) => {
74
+ conversation = c;
75
+ });
76
+ });
77
+
78
+ let hashTestText = '#test.txt';
79
+ let sampleImageSmallOnePng = 'sample-image-small-one.png';
80
+ let sampleImageSmallTwoPng = 'sample-image-small-two.png';
81
+ let sampleImageLargeJpg = 'sample-image-large.jpg';
82
+ let sampleImageLargeNoEXIFJpg = 'sample-image-large-no-exif.jpg';
83
+ let samplePowerpointTwoPagePpt = 'sample-powerpoint-two-page.ppt';
84
+ let sampleTextOne = 'sample-text-one.txt';
85
+ let sampleTextTwo = 'sample-text-two.txt';
86
+ const sampleGif = 'sample-gif.gif';
87
+
88
+ before(() =>
89
+ Promise.all([
90
+ fh.fetchWithoutMagic(hashTestText),
91
+ fh.fetchWithoutMagic(sampleImageSmallOnePng),
92
+ fh.fetchWithoutMagic(sampleImageSmallTwoPng),
93
+ fh.fetchWithoutMagic(sampleImageLargeJpg),
94
+ fh.fetchWithoutMagic(sampleImageLargeNoEXIFJpg),
95
+ fh.fetchWithoutMagic(samplePowerpointTwoPagePpt),
96
+ fh.fetchWithoutMagic(sampleTextOne),
97
+ fh.fetchWithoutMagic(sampleTextTwo),
98
+ ]).then((res) => {
99
+ [
100
+ hashTestText,
101
+ sampleImageSmallOnePng,
102
+ sampleImageSmallTwoPng,
103
+ sampleImageLargeJpg,
104
+ sampleImageLargeNoEXIFJpg,
105
+ samplePowerpointTwoPagePpt,
106
+ sampleTextOne,
107
+ sampleTextTwo,
108
+ ] = res;
109
+ })
110
+ );
111
+
112
+ describe('#share()', () => {
113
+ it('shares the specified file to the specified conversation', () =>
114
+ webex.internal.conversation
115
+ .share(conversation, [sampleTextOne])
116
+ .then((activity) => {
117
+ assert.isActivity(activity);
118
+ assert.isEncryptedActivity(activity);
119
+ assert.isFileItem(activity.object.files.items[0]);
120
+
121
+ return webex.internal.conversation.download(activity.object.files.items[0]);
122
+ })
123
+ .then(returnFirstArg((f) => assert.match(f.type, /text\/plain/)))
124
+ .then((f) =>
125
+ fh.isMatchingFile(f, sampleTextOne).then((result) => assert.isTrue(result))
126
+ ));
127
+
128
+ it('shares the specified set of files to the specified conversation', () =>
129
+ webex.internal.conversation
130
+ .share(conversation, [sampleTextOne, sampleTextTwo])
131
+ .then((activity) => {
132
+ assert.isActivity(activity);
133
+ assert.isEncryptedActivity(activity);
134
+ assert.isFileItem(activity.object.files.items[0]);
135
+ assert.isFileItem(activity.object.files.items[1]);
136
+
137
+ return Promise.all([
138
+ webex.internal.conversation
139
+ .download(activity.object.files.items[0])
140
+ .then(returnFirstArg((f) => assert.match(f.type, /text\/plain/))),
141
+ webex.internal.conversation
142
+ .download(activity.object.files.items[1])
143
+ .then(returnFirstArg((f) => assert.match(f.type, /text\/plain/))),
144
+ ]);
145
+ })
146
+ .then(([file0, file1]) =>
147
+ Promise.all([
148
+ fh.isMatchingFile(file0, sampleTextOne).then((result) => assert.isTrue(result)),
149
+ fh.isMatchingFile(file1, sampleTextTwo).then((result) => assert.isTrue(result)),
150
+ ])
151
+ ));
152
+
153
+ describe('files with special characters', () => {
154
+ it('shares the specified file to the specified conversation', () =>
155
+ webex.internal.conversation
156
+ .share(conversation, [hashTestText])
157
+ .then((activity) => {
158
+ assert.isActivity(activity);
159
+ assert.isEncryptedActivity(activity);
160
+ assert.isFileItem(activity.object.files.items[0]);
161
+
162
+ return webex.internal.conversation.download(activity.object.files.items[0]);
163
+ })
164
+ // in node, this'll be 'text/plain', in a browser, it'll be
165
+ // 'text/html'. I'm pretty sure it's caused by the # convincing
166
+ // express it's a hashroute and treating it as html. The discrepancy
167
+ // has no bearing on the test's validity. Further, we need to use
168
+ // match rather than equal because some browser append the charset.
169
+ .then(returnFirstArg((f) => assert.match(f.type, hashTestText.type || /text\/plain/)))
170
+ .then((f) =>
171
+ fh.isMatchingFile(f, hashTestText).then((result) => assert.isTrue(result))
172
+ ));
173
+ });
174
+
175
+ it('shares an image with no EXIF data to the specified conversation and correctly error handles', () =>
176
+ webex.internal.conversation
177
+ .share(conversation, [sampleImageLargeNoEXIFJpg])
178
+ .then((activity) => {
179
+ assert.isActivity(activity);
180
+ assert.isEncryptedActivity(activity);
181
+
182
+ const fileItem = activity.object.files.items[0];
183
+
184
+ assert.isFileItem(fileItem);
185
+
186
+ const thumbnailItem = activity.object.files.items[0].image;
187
+
188
+ assert.isThumbnailItem(thumbnailItem);
189
+ assert.equal(thumbnailItem.width, 640);
190
+ assert.isAbove(thumbnailItem.height, 358);
191
+ assert.isBelow(thumbnailItem.height, 361);
192
+
193
+ return webex.internal.conversation.download(activity.object.files.items[0]);
194
+ })
195
+ .then(returnFirstArg((f) => assert.equal(f.type, 'image/jpeg')))
196
+ .then((f) =>
197
+ fh.isMatchingFile(f, sampleImageLargeNoEXIFJpg).then((result) => assert.isTrue(result))
198
+ ));
199
+
200
+ it('shares the specified image to the specified conversation', () =>
201
+ webex.internal.conversation
202
+ .share(conversation, [sampleImageLargeJpg])
203
+ .then((activity) => {
204
+ assert.isActivity(activity);
205
+ assert.isEncryptedActivity(activity);
206
+
207
+ const fileItem = activity.object.files.items[0];
208
+
209
+ assert.isFileItem(fileItem);
210
+
211
+ const thumbnailItem = activity.object.files.items[0].image;
212
+
213
+ assert.isThumbnailItem(thumbnailItem);
214
+ assert.equal(thumbnailItem.width, 640);
215
+ assert.isAbove(thumbnailItem.height, 330);
216
+ assert.isBelow(thumbnailItem.height, 361);
217
+
218
+ return webex.internal.conversation.download(activity.object.files.items[0]);
219
+ })
220
+ .then(returnFirstArg((f) => assert.equal(f.type, 'image/jpeg')))
221
+ .then((f) =>
222
+ fh.isMatchingFile(f, sampleImageLargeJpg).then((result) => assert.isTrue(result))
223
+ ));
224
+
225
+ it('shares the specified set of images the specified conversation', () =>
226
+ webex.internal.conversation
227
+ .share(conversation, [sampleImageSmallOnePng, sampleImageSmallTwoPng])
228
+ .then((activity) => {
229
+ assert.isActivity(activity);
230
+ assert.isEncryptedActivity(activity);
231
+ assert.isFileItem(activity.object.files.items[0]);
232
+ assert.isFileItem(activity.object.files.items[1]);
233
+ assert.isThumbnailItem(activity.object.files.items[0].image);
234
+ assert.isThumbnailItem(activity.object.files.items[1].image);
235
+
236
+ return Promise.all([
237
+ webex.internal.conversation
238
+ .download(activity.object.files.items[0])
239
+ .then(returnFirstArg((f) => assert.equal(f.type, 'image/png'))),
240
+ webex.internal.conversation
241
+ .download(activity.object.files.items[1])
242
+ .then(returnFirstArg((f) => assert.equal(f.type, 'image/png'))),
243
+ ]);
244
+ })
245
+ .then(([file0, file1]) =>
246
+ Promise.all([
247
+ fh
248
+ .isMatchingFile(file0, sampleImageSmallOnePng)
249
+ .then((result) => assert.isTrue(result)),
250
+ fh
251
+ .isMatchingFile(file1, sampleImageSmallTwoPng)
252
+ .then((result) => assert.isTrue(result)),
253
+ ])
254
+ ));
255
+
256
+ describe('when it shares a transcodable file', () => {
257
+ let activities;
258
+ let blockUntilTranscode;
259
+ let clientTempId;
260
+ let objectUrl;
261
+
262
+ beforeEach(() => {
263
+ clientTempId = uuid.v4();
264
+ activities = [];
265
+ webex.internal.mercury.on('event:conversation.activity', onMessage);
266
+ blockUntilTranscode = new Defer();
267
+ });
268
+
269
+ afterEach(
270
+ () => webex && webex.internal.mercury.off('event:conversation.activity', onMessage)
271
+ );
272
+
273
+ function onMessage(message) {
274
+ activities.push(message.data.activity);
275
+
276
+ if (message.data.activity.clientTempId === clientTempId) {
277
+ objectUrl = message.data.activity.object.url;
278
+ }
279
+
280
+ if (objectUrl) {
281
+ const updateActivity = find(
282
+ activities,
283
+ (activity) => activity.verb === 'update' && activity.object.url === objectUrl
284
+ );
285
+
286
+ if (updateActivity) {
287
+ blockUntilTranscode.resolve(updateActivity);
288
+ }
289
+ }
290
+ }
291
+
292
+ // doesn't seem like we get mercury event back to update transcoded file in time
293
+ // https://jira-eng-gpk2.cisco.com/jira/browse/SPARK-166178
294
+ it.skip('mercury receives an update', () =>
295
+ webex.internal.conversation
296
+ .share(conversation, {
297
+ object: {
298
+ files: [samplePowerpointTwoPagePpt],
299
+ },
300
+ clientTempId,
301
+ })
302
+ .then((activity) => {
303
+ assert.equal(activity.clientTempId, clientTempId);
304
+ activities.push(activity);
305
+
306
+ return webex.internal.conversation
307
+ .download(activity.object.files.items[0])
308
+ .then((f) => assert.equal(f.type, 'application/vnd.ms-powerpoint'))
309
+ .then(() => blockUntilTranscode.promise)
310
+ .then((updateActivity) => {
311
+ assert.equal(updateActivity.object.url, activity.object.url);
312
+ assert.lengthOf(
313
+ updateActivity.object.files.items[0].transcodedCollection.items[0].files.items,
314
+ 2
315
+ );
316
+ // Prove that the newly transcoded file can be downloaded and
317
+ // decrypted
318
+ const firstItem =
319
+ updateActivity.object.files.items[0].transcodedCollection.items[0].files
320
+ .items[0];
321
+
322
+ return webex.internal.conversation.download(firstItem);
323
+ });
324
+ }));
325
+ });
326
+
327
+ it('shares a whiteboard', () => {
328
+ const activity = webex.internal.conversation.makeShare(conversation);
329
+
330
+ activity.add(sampleImageSmallOnePng, {
331
+ actions: [
332
+ {
333
+ type: 'edit',
334
+ mimeType: 'application/x-cisco-webex-whiteboard',
335
+ url: 'https://boards.example.com/boards/1',
336
+ },
337
+ ],
338
+ });
339
+
340
+ return webex.internal.conversation
341
+ .share(conversation, activity)
342
+ .then((share) => {
343
+ assert.isActivity(share);
344
+ assert.isEncryptedActivity(share);
345
+ assert.isFileItem(share.object.files.items[0]);
346
+ assert.isThumbnailItem(share.object.files.items[0].image);
347
+ assert.equal(share.object.contentCategory, 'documents');
348
+ assert.isArray(share.object.files.items[0].actions);
349
+ assert.equal(share.object.files.items[0].actions[0].type, 'edit');
350
+ assert.equal(
351
+ share.object.files.items[0].actions[0].mimeType,
352
+ 'application/x-cisco-webex-whiteboard'
353
+ );
354
+ assert.equal(
355
+ share.object.files.items[0].actions[0].url,
356
+ 'https://boards.example.com/boards/1'
357
+ );
358
+
359
+ return webex.internal.conversation
360
+ .download(share.object.files.items[0])
361
+ .then(returnFirstArg((f) => assert.equal(f.type, 'image/png')));
362
+ })
363
+ .then((file0) =>
364
+ fh.isMatchingFile(file0, sampleImageSmallOnePng).then((result) => assert.isTrue(result))
365
+ );
366
+ });
367
+ });
368
+
369
+ describe('#makeShare', () => {
370
+ // http-core doesn't current do upload progress events in node, so this
371
+ // test is browser-only for now
372
+ skipInNode(flaky(it, process.env.SKIP_FLAKY_TESTS))(
373
+ 'provides an interface for file upload events',
374
+ () => {
375
+ const spy = sinon.spy();
376
+ const share = webex.internal.conversation.makeShare(conversation);
377
+ const emitter = share.add(sampleImageSmallOnePng);
378
+
379
+ emitter.on('progress', spy);
380
+
381
+ return webex.internal.conversation
382
+ .share(conversation, share)
383
+ .then(() => assert.called(spy));
384
+ }
385
+ );
386
+
387
+ it('shares a file with a name', () => {
388
+ const share = webex.internal.conversation.makeShare(conversation);
389
+
390
+ share.add(sampleImageSmallOnePng);
391
+ share.object = {
392
+ displayName: 'a name',
393
+ };
394
+
395
+ return webex.internal.conversation
396
+ .share(conversation, share)
397
+ .then((activity) => {
398
+ assert.equal(activity.object.displayName, 'a name');
399
+
400
+ return webex.internal.conversation.download(activity.object.files.items[0]);
401
+ })
402
+ .then(returnFirstArg((f) => assert.equal(f.type, 'image/png')))
403
+ .then((file) => fh.isMatchingFile(file, sampleImageSmallOnePng));
404
+ });
405
+
406
+ it('allows removal of a file from the share', () => {
407
+ const share = webex.internal.conversation.makeShare(conversation);
408
+
409
+ share.add(sampleImageSmallOnePng);
410
+ share.add(sampleImageSmallTwoPng);
411
+ share.remove(sampleImageSmallOnePng);
412
+ share.object = {
413
+ displayName: 'a name',
414
+ };
415
+
416
+ return webex.internal.conversation
417
+ .share(conversation, share)
418
+ .then((activity) => {
419
+ assert.equal(activity.object.displayName, 'a name');
420
+ assert.lengthOf(activity.object.files.items, 1);
421
+
422
+ return webex.internal.conversation.download(activity.object.files.items[0]);
423
+ })
424
+ .then(returnFirstArg((f) => assert.equal(f.type, 'image/png')))
425
+ .then((file) => fh.isMatchingFile(file, sampleImageSmallTwoPng));
426
+ });
427
+
428
+ it('shares a file to a thread', () => {
429
+ const share = webex.internal.conversation.makeShare(conversation);
430
+
431
+ share.add(sampleImageSmallOnePng);
432
+ share.object = {
433
+ displayName: 'a name',
434
+ };
435
+
436
+ let parentActivityId;
437
+
438
+ return webex.internal.conversation
439
+ .share(conversation, share)
440
+ .then((activity) => {
441
+ assert.equal(activity.object.displayName, 'a name');
442
+ const threadShare = webex.internal.conversation.makeShare(conversation);
443
+
444
+ threadShare.add(sampleImageSmallOnePng);
445
+ threadShare.object = {
446
+ displayName: 'a thread share name',
447
+ };
448
+ threadShare.activityType = 'reply';
449
+ threadShare.parentActivityId = activity.id;
450
+ parentActivityId = activity.id;
451
+
452
+ return webex.internal.conversation.share(conversation, threadShare);
453
+ })
454
+ .then((activity) => {
455
+ const {id, type} = activity.parent;
456
+
457
+ assert.equal(id, parentActivityId);
458
+ assert.equal(type, 'reply');
459
+
460
+ return webex.internal.conversation.download(activity.object.files.items[0]);
461
+ })
462
+ .then(returnFirstArg((f) => assert.equal(f.type, 'image/png')))
463
+ .then((file) => fh.isMatchingFile(file, sampleImageSmallOnePng));
464
+ });
465
+ });
466
+
467
+ describe('#addGif', () => {
468
+ let blob, buffer;
469
+
470
+ // Read file as buffer
471
+ browserOnly(before)(() =>
472
+ fh.fetch(sampleGif).then((file) => {
473
+ blob = file;
474
+
475
+ return new Promise((resolve) => {
476
+ /* global FileReader */
477
+ const fileReader = new FileReader();
478
+
479
+ fileReader.onload = function () {
480
+ buffer = this.result;
481
+ resolve();
482
+ };
483
+ fileReader.readAsArrayBuffer(blob);
484
+ });
485
+ })
486
+ );
487
+
488
+ browserOnly(it)(
489
+ 'if the giphy does not exist, then we check it gets added to this.uploads',
490
+ (done) => {
491
+ // eslint-disable-next-line no-undef
492
+ const file = new File([buffer], blob.name, {type: 'image/gif'});
493
+
494
+ const originalGiphyURL = 'https://media1.giphy.com/media/nXxOjZrbnbRxS/giphy.gif';
495
+ const originalGiphyStillURL = 'https://media1.giphy.com/media/nXxOjZrbnbRxS/giphy_s.gif';
496
+ const url = 'https://giphy.com';
497
+
498
+ // simulate in web client where
499
+ Object.defineProperty(file, 'url', {value: originalGiphyURL});
500
+ // define thumbnail
501
+ Object.defineProperty(file, 'image', {
502
+ value: {
503
+ height: file.width,
504
+ width: file.height,
505
+ url: originalGiphyStillURL,
506
+ },
507
+ });
508
+
509
+ const share = webex.internal.conversation.makeShare(conversation);
510
+
511
+ // Check that initially there were no uploads
512
+ assert.isTrue(share.uploads.size === 0);
513
+ share.addGif(file).then(() => {
514
+ assert.equal(share.uploads.size, 1);
515
+ assert.equal(share.uploads.get(file).objectType, 'file');
516
+ assert.equal(share.uploads.get(file).displayName, sampleGif);
517
+ assert.equal(share.uploads.get(file).mimeType, 'image/gif');
518
+ assert.equal(share.uploads.get(file).fileSize, 473119);
519
+ assert.equal(share.uploads.get(file).width, 200);
520
+ assert.equal(share.uploads.get(file).height, 270);
521
+ assert.equal(share.uploads.get(file).url, url);
522
+ assert.exists(share.uploads.get(file).scr);
523
+ assert.equal(share.uploads.get(file).scr.loc, originalGiphyURL);
524
+
525
+ assert.exists(share.uploads.get(file).image);
526
+ assert.equal(share.uploads.get(file).image.width, 200);
527
+ assert.equal(share.uploads.get(file).image.height, 270);
528
+ assert.equal(share.uploads.get(file).image.url, url);
529
+ assert.exists(share.uploads.get(file).image.scr);
530
+ assert.equal(share.uploads.get(file).image.scr.loc, originalGiphyStillURL);
531
+ });
532
+ done();
533
+ }
534
+ );
535
+ });
536
+ });
537
+ });