@webex/plugin-meetings 2.27.0 → 2.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webex/plugin-meetings",
3
- "version": "2.27.0",
3
+ "version": "2.28.0",
4
4
  "description": "",
5
5
  "license": "MIT",
6
6
  "contributors": [
@@ -24,15 +24,15 @@
24
24
  },
25
25
  "dependencies": {
26
26
  "@babel/runtime-corejs2": "^7.14.8",
27
- "@webex/webex-core": "2.27.0",
28
- "@webex/internal-plugin-mercury": "2.27.0",
29
- "@webex/internal-plugin-conversation": "2.27.0",
27
+ "@webex/webex-core": "2.28.0",
28
+ "@webex/internal-plugin-mercury": "2.28.0",
29
+ "@webex/internal-plugin-conversation": "2.28.0",
30
30
  "webrtc-adapter": "^7.7.0",
31
31
  "lodash": "^4.17.21",
32
32
  "uuid": "^3.3.2",
33
33
  "global": "^4.4.0",
34
34
  "ip-anonymize": "^0.1.0",
35
- "@webex/common": "2.27.0",
35
+ "@webex/common": "2.28.0",
36
36
  "bowser": "^2.11.0",
37
37
  "sdp-transform": "^2.12.0",
38
38
  "btoa": "^1.2.1",
package/src/constants.ts CHANGED
@@ -302,6 +302,7 @@ export const EVENT_TRIGGERS = {
302
302
  MEETING_ACTIONS_UPDATE: 'meeting:actionsUpdate',
303
303
  MEETING_STATE_CHANGE: 'meeting:stateChange',
304
304
  MEETING_MEETING_CONTAINER_UPDATE: 'meeting:meetingContainer:update',
305
+ MEETING_EMBEDDED_APPS_UPDATE: 'meeting:embeddedApps:update',
305
306
  MEDIA_QUALITY: 'media:quality',
306
307
  MEETINGS_NETWORK_DISCONNECTED: 'network:disconnected',
307
308
  MEETINGS_NETWORK_CONNECTED: 'network:connected',
@@ -508,7 +509,8 @@ export const LOCUSINFO = {
508
509
  MEETING_LOCKED: 'MEETING_LOCKED',
509
510
  MEETING_UNLOCKED: 'MEETING_UNLOCKED',
510
511
  SELF_OBSERVING: 'SELF_OBSERVING',
511
- DISCONNECT_DUE_TO_INACTIVITY: 'DISCONNECT_DUE_TO_INACTIVITY'
512
+ DISCONNECT_DUE_TO_INACTIVITY: 'DISCONNECT_DUE_TO_INACTIVITY',
513
+ EMBEDDED_APPS_UPDATED: 'EMBEDDED_APPS_UPDATED'
512
514
  }
513
515
  };
514
516
 
@@ -1053,3 +1055,8 @@ export const BNR_STATUS = {
1053
1055
  SHOULD_DISABLE: 'SHOULD_DISABLE',
1054
1056
  NOT_ENABLED: 'NOT_ENABLED'
1055
1057
  };
1058
+
1059
+ export const EMBEDDED_APP_TYPES = {
1060
+ SLIDO: 'SLIDO',
1061
+ OTHER: 'OTHER'
1062
+ };
@@ -0,0 +1,51 @@
1
+ import {EMBEDDED_APP_TYPES} from '../constants';
2
+
3
+ const EmbeddedAppsUtils = {};
4
+
5
+ const SLIDO_REGEX = /.sli.do\//;
6
+
7
+ /**
8
+ * Parse the relevant values that we care about
9
+ * @param {Object} embeddedApp - raw embedded app object
10
+ * @returns {Object} parsedObject - parsed embedded app object
11
+ */
12
+ EmbeddedAppsUtils.parseApp = (embeddedApp) => {
13
+ const parsedApp = {...embeddedApp};
14
+
15
+ parsedApp.type = EMBEDDED_APP_TYPES.OTHER;
16
+ const url = parsedApp.instanceInfo?.appInstanceUrl;
17
+
18
+ if (url && url.match(SLIDO_REGEX)) {
19
+ parsedApp.type = EMBEDDED_APP_TYPES.SLIDO;
20
+ }
21
+
22
+ return parsedApp;
23
+ };
24
+
25
+ /**
26
+ * Determines if two embedded apps arrays are similar.
27
+ * NOTE: This is a simple test for performance reasons.
28
+ * @param {any[]} apps1 - an array of apps
29
+ * @param {any[]} apps2 - an array of apps
30
+ * @returns {boolean} true if the arrays are different
31
+ */
32
+ EmbeddedAppsUtils.areSimilar = (apps1, apps2) => {
33
+ if (apps1?.length !== apps2?.length) {
34
+ return false;
35
+ }
36
+ if (apps1?.[0]?.state !== apps2?.[0]?.state) {
37
+ return false;
38
+ }
39
+
40
+ return true;
41
+ };
42
+
43
+ /**
44
+ * Parse the array of embedded apps
45
+ * @param {array} embeddedApps
46
+ * @returns {array} result - new array of parsed embedded app objects
47
+ */
48
+ EmbeddedAppsUtils.parse = (embeddedApps) => embeddedApps && embeddedApps.map(EmbeddedAppsUtils.parseApp);
49
+
50
+
51
+ export default EmbeddedAppsUtils;
@@ -24,6 +24,7 @@ import FullState from '../locus-info/fullState';
24
24
  import SelfUtils from '../locus-info/selfUtils';
25
25
  import HostUtils from '../locus-info/hostUtils';
26
26
  import ControlsUtils from '../locus-info/controlsUtils';
27
+ import EmbeddedAppsUtils from '../locus-info/embeddedAppsUtils';
27
28
  import MediaSharesUtils from '../locus-info/mediaSharesUtils';
28
29
  import LocusDeltaParser from '../locus-info/parser';
29
30
 
@@ -151,6 +152,7 @@ export default class LocusInfo extends EventsScope {
151
152
  this.updateLocusUrl(locus.url);
152
153
  this.updateFullState(locus.fullState);
153
154
  this.updateMeetingInfo(locus.info);
155
+ this.updateEmbeddedApps(locus.embeddedApps);
154
156
  // self and participants generate sipUrl for 1:1 meeting
155
157
  this.updateSelf(locus.self, locus.participants);
156
158
  this.updateHostInfo(locus.host);
@@ -315,6 +317,7 @@ export default class LocusInfo extends EventsScope {
315
317
  this.updateSequence(locus.sequence);
316
318
  this.updateMemberShip(locus.membership);
317
319
  this.updateIdentifiers(locus.identities);
320
+ this.updateEmbeddedApps(locus.embeddedApps);
318
321
  this.compareAndUpdate();
319
322
  // update which required to compare different objects from locus
320
323
  }
@@ -841,6 +844,30 @@ export default class LocusInfo extends EventsScope {
841
844
  }
842
845
  }
843
846
 
847
+ /**
848
+ * @param {Object} embeddedApps
849
+ * @returns {undefined}
850
+ * @memberof LocusInfo
851
+ */
852
+ updateEmbeddedApps(embeddedApps) {
853
+ // don't do anything if the arrays of apps haven't changed significantly
854
+ if (EmbeddedAppsUtils.areSimilar(this.embeddedApps, embeddedApps)) {
855
+ return;
856
+ }
857
+
858
+ this.parsedLocus.embeddedApps = EmbeddedAppsUtils.parse(embeddedApps);
859
+
860
+ this.emitScoped(
861
+ {
862
+ file: 'locus-info',
863
+ function: 'updateEmbeddedApps'
864
+ },
865
+ LOCUSINFO.EVENTS.EMBEDDED_APPS_UPDATED,
866
+ embeddedApps
867
+ );
868
+ this.embeddedApps = embeddedApps;
869
+ }
870
+
844
871
  /**
845
872
  * handles when the locus.mediaShares is updated
846
873
  * @param {Object} mediaShares the locus.mediaShares property
@@ -1065,6 +1065,7 @@ export default class Meeting extends StatelessWebexPlugin {
1065
1065
  this.setUpLocusParticipantsListener();
1066
1066
  this.setupLocusControlsListener();
1067
1067
  this.setUpLocusMediaSharesListener();
1068
+ this.setUpLocusEmbeddedAppsListener();
1068
1069
  this.setUpLocusInfoMeetingInfoListener();
1069
1070
  this.setUpLocusInfoAssignHostListener();
1070
1071
  this.setUpLocusInfoMediaInactiveListener();
@@ -1905,6 +1906,28 @@ export default class Meeting extends StatelessWebexPlugin {
1905
1906
  });
1906
1907
  }
1907
1908
 
1909
+ /**
1910
+ * Set up the locus info embedded apps listener
1911
+ * @returns {undefined}
1912
+ * @private
1913
+ * @memberof meeting
1914
+ */
1915
+ setUpLocusEmbeddedAppsListener() {
1916
+ this.locusInfo.on(LOCUSINFO.EVENTS.EMBEDDED_APPS_UPDATED, (embeddedApps) => {
1917
+ if (embeddedApps) {
1918
+ Trigger.trigger(
1919
+ this,
1920
+ {
1921
+ file: 'meeting/index',
1922
+ function: 'setUpLocusEmbeddedAppsListener'
1923
+ },
1924
+ EVENT_TRIGGERS.MEETING_EMBEDDED_APPS_UPDATE,
1925
+ embeddedApps
1926
+ );
1927
+ }
1928
+ });
1929
+ }
1930
+
1908
1931
  /**
1909
1932
  * Internal function to listen to the self object changes
1910
1933
  * @returns {undefined}
@@ -0,0 +1,102 @@
1
+ import {assert} from '@webex/test-helper-chai';
2
+ import {cloneDeep} from 'lodash';
3
+
4
+ import EmbeddedAppUtils from '@webex/plugin-meetings/src/locus-info/embeddedAppsUtils';
5
+
6
+ describe('plugin-meetings', () => {
7
+ describe('embeddedAppsUtils', () => {
8
+ const slidoApp = {
9
+ url: 'https://hecate-b.wbx2.com/apps/api/v1/locus/7a4994a7',
10
+ sequence: 138849877016800000,
11
+ appId: 'Y2lzY29zcGFyazovL3VzL0FQUExJQ0FUSU9OLzQxODc1MGQ0LTM3ZDctNGY2MC1hOWE3LWEwZTE1NDFhNjRkNg',
12
+ instanceInfo: {
13
+ appInstanceUrl: 'https://webex.sli.do/participant/event/mFKKjcYxzx9h31eyWgngFS?clusterId=eu1',
14
+ externalAppInstanceUrl: '',
15
+ title: 'Active session'
16
+ },
17
+ state: 'STARTED',
18
+ lastModified: '2022-10-13T21:01:41.680Z'
19
+ };
20
+ const otherApp = {
21
+ url: 'https://hecate-b.wbx2.com/apps/api/v1/locus/7a4994a7',
22
+ sequence: 138849877016800000,
23
+ appId: 'some-other-app-id',
24
+ instanceInfo: {
25
+ appInstanceUrl: 'https://webex.someotherapp.com/mFKKjcYxzx9h31eyWgngFS?clusterId=eu1',
26
+ externalAppInstanceUrl: '',
27
+ title: 'Active session'
28
+ },
29
+ state: 'STARTED',
30
+ lastModified: '2022-10-13T21:01:31.680Z'
31
+ };
32
+
33
+ describe('parseApp', () => {
34
+ it('returns a parsed embedded app with type of SLIDO', () => {
35
+ const parsedApp = EmbeddedAppUtils.parseApp(slidoApp);
36
+ const expectedApp = {...slidoApp, ...{type: 'SLIDO'}};
37
+
38
+ assert.deepEqual(parsedApp, expectedApp);
39
+ });
40
+
41
+ it('returns a parsed embedded app with type of OTHER', () => {
42
+ const parsedApp = EmbeddedAppUtils.parseApp(otherApp);
43
+ const expectedApp = {...otherApp, ...{type: 'OTHER'}};
44
+
45
+ assert.deepEqual(parsedApp, expectedApp);
46
+ });
47
+ });
48
+
49
+ describe('parse', () => {
50
+ it('returns a copy of embeddedApps', () => {
51
+ const embeddedApps = [slidoApp];
52
+ const parsedApps = EmbeddedAppUtils.parse(embeddedApps);
53
+
54
+ assert.notStrictEqual(parsedApps, embeddedApps);
55
+ assert.equal(parsedApps.length, embeddedApps.length);
56
+ });
57
+ });
58
+
59
+ describe('areSimilar', () => {
60
+ it('returns true if the apps are the same', () => {
61
+ const apps1 = [slidoApp];
62
+ const apps2 = [cloneDeep(slidoApp)];
63
+
64
+ const different = EmbeddedAppUtils.areSimilar(apps1, apps2);
65
+
66
+ assert.equal(different, true);
67
+ });
68
+
69
+ it('returns false if the number of apps is different', () => {
70
+ const apps1 = [slidoApp];
71
+ const apps2 = [cloneDeep(slidoApp), cloneDeep(slidoApp)];
72
+
73
+ const different = EmbeddedAppUtils.areSimilar(apps1, apps2);
74
+
75
+ assert.equal(different, false);
76
+ });
77
+
78
+ it('returns false if the state of the first apps is different', () => {
79
+ const apps1 = [slidoApp];
80
+ const apps2 = [cloneDeep(slidoApp)];
81
+
82
+ apps2[0].state = 'STOPPED';
83
+
84
+ const different = EmbeddedAppUtils.areSimilar(apps1, apps2);
85
+
86
+ assert.equal(different, false);
87
+ });
88
+
89
+ it('handles null apps', () => {
90
+ assert.equal(EmbeddedAppUtils.areSimilar(null, null), true);
91
+ assert.equal(EmbeddedAppUtils.areSimilar(null, [slidoApp]), false);
92
+ assert.equal(EmbeddedAppUtils.areSimilar([slidoApp], null), false);
93
+ });
94
+
95
+ it('handles empty apps', () => {
96
+ assert.equal(EmbeddedAppUtils.areSimilar([], []), true);
97
+ assert.equal(EmbeddedAppUtils.areSimilar([], [slidoApp]), false);
98
+ assert.equal(EmbeddedAppUtils.areSimilar([slidoApp], []), false);
99
+ });
100
+ });
101
+ });
102
+ });
@@ -828,6 +828,60 @@ describe('plugin-meetings', () => {
828
828
  });
829
829
  });
830
830
 
831
+ describe('#updateEmbeddedApps()', () => {
832
+ const newEmbeddedApps = [{
833
+ url: 'https://hecate-b.wbx2.com/apps/api/v1/locus/7a4994a7',
834
+ sequence: 138849877016800000,
835
+ appId: 'Y2lzY29zcGFyazovL3VzL0FQUExJQ0FUSU9OLzQxODc1MGQ0LTM3ZDctNGY2MC1hOWE3LWEwZTE1NDFhNjRkNg',
836
+ instanceInfo: {
837
+ appInstanceUrl: 'https://webex.sli.do/participant/event/mFKKjcYxzx9h31eyWgngFS?clusterId=eu1',
838
+ externalAppInstanceUrl: '',
839
+ title: 'Active session'
840
+ },
841
+ state: 'STARTED',
842
+ lastModified: '2022-10-13T21:01:41.680Z'
843
+ }];
844
+
845
+ it('updates the embeddedApps object', () => {
846
+ const prev = locusInfo.embeddedApps;
847
+
848
+ locusInfo.updateEmbeddedApps(newEmbeddedApps);
849
+
850
+ assert.notEqual(locusInfo.embeddedApps, prev);
851
+ });
852
+
853
+ it('does not emit EMBEDDED_APPS_UPDATED when apps didn\'t change', () => {
854
+ locusInfo.updateEmbeddedApps(newEmbeddedApps);
855
+
856
+ locusInfo.emitScoped = sinon.stub();
857
+
858
+ const clonedApps = cloneDeep(newEmbeddedApps);
859
+
860
+ locusInfo.updateEmbeddedApps(clonedApps);
861
+
862
+ assert.notCalled(locusInfo.emitScoped);
863
+ });
864
+
865
+ it('emits EMBEDDED_APPS_UPDATED when apps changed', () => {
866
+ locusInfo.updateEmbeddedApps(newEmbeddedApps);
867
+
868
+ locusInfo.emitScoped = sinon.stub();
869
+
870
+ const clonedApps = cloneDeep(newEmbeddedApps);
871
+
872
+ clonedApps[0].state = 'STOPPED';
873
+
874
+ locusInfo.updateEmbeddedApps(clonedApps);
875
+
876
+ assert.calledWith(locusInfo.emitScoped, {
877
+ file: 'locus-info',
878
+ function: 'updateEmbeddedApps'
879
+ },
880
+ LOCUSINFO.EVENTS.EMBEDDED_APPS_UPDATED,
881
+ clonedApps);
882
+ });
883
+ });
884
+
831
885
  describe('#LocusDeltaEvents', () => {
832
886
  const fakeMeeting = 'fakeMeeting';
833
887
  let sandbox = null;