@webex/plugin-meetings 3.0.0-beta.34 → 3.0.0-beta.35

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.
@@ -23,10 +23,10 @@ import {
23
23
  PARTICIPANT,
24
24
  PROVISIONAL_TYPE_DIAL_IN,
25
25
  PROVISIONAL_TYPE_DIAL_OUT,
26
+ REACHABILITY,
26
27
  SEND_DTMF_ENDPOINT,
27
28
  _SLIDES_,
28
29
  } from '../constants';
29
- import {Reaction} from '../reactions/reactions.type';
30
30
  import {SendReactionOptions, ToggleReactionsOptions} from './request.type';
31
31
 
32
32
  /**
@@ -35,6 +35,11 @@ import {SendReactionOptions, ToggleReactionsOptions} from './request.type';
35
35
  export default class MeetingRequest extends StatelessWebexPlugin {
36
36
  changeVideoLayoutDebounced: any;
37
37
 
38
+ /**
39
+ * Constructor
40
+ * @param {Object} attrs
41
+ * @param {Object} options
42
+ */
38
43
  constructor(attrs: any, options: any) {
39
44
  super(attrs, options);
40
45
  this.changeVideoLayoutDebounced = debounce(this.changeVideoLayout, 2000, {
@@ -43,6 +48,32 @@ export default class MeetingRequest extends StatelessWebexPlugin {
43
48
  });
44
49
  }
45
50
 
51
+ /**
52
+ * Returns joinCookie from boundedStorage if present.
53
+ * @returns {Object} joinCookie
54
+ */
55
+ private getJoinCookie = async () => {
56
+ // @ts-ignore
57
+ const joinCookieRaw = await this.webex.boundedStorage
58
+ .get(REACHABILITY.namespace, REACHABILITY.localStorageJoinCookie)
59
+ .catch(() => {});
60
+
61
+ if (joinCookieRaw) {
62
+ try {
63
+ const joinCookie = JSON.parse(joinCookieRaw);
64
+ if (joinCookie) {
65
+ return joinCookie;
66
+ }
67
+ } catch (e) {
68
+ LoggerProxy.logger.error(
69
+ `MeetingRequest#constructor --> Error in parsing join cookie data: ${e}`
70
+ );
71
+ }
72
+ }
73
+
74
+ return null;
75
+ };
76
+
46
77
  /**
47
78
  * Make a network request to join a meeting
48
79
  * @param {Object} options
@@ -99,6 +130,8 @@ export default class MeetingRequest extends StatelessWebexPlugin {
99
130
 
100
131
  let url = '';
101
132
 
133
+ const joinCookie = await this.getJoinCookie();
134
+
102
135
  const body: any = {
103
136
  asResourceOccupant,
104
137
  device: {
@@ -115,6 +148,7 @@ export default class MeetingRequest extends StatelessWebexPlugin {
115
148
  supportsNativeLobby: 1,
116
149
  clientMediaPreferences: {
117
150
  preferTranscoding: preferTranscoding ?? true,
151
+ joinCookie,
118
152
  },
119
153
  };
120
154
 
@@ -63,11 +63,13 @@ export default class Reachability {
63
63
 
64
64
  // Remove stored reachability results to ensure no stale data
65
65
  // @ts-ignore
66
- await this.webex.boundedStorage.del(this.namespace, REACHABILITY.localStorage);
66
+ await this.webex.boundedStorage.del(this.namespace, REACHABILITY.localStorageResult);
67
+ // @ts-ignore
68
+ await this.webex.boundedStorage.del(this.namespace, REACHABILITY.localStorageJoinCookie);
67
69
 
68
70
  // Fetch clusters and measure latency
69
71
  try {
70
- const clusters = await this.reachabilityRequest.getClusters();
72
+ const {clusters, joinCookie} = await this.reachabilityRequest.getClusters();
71
73
 
72
74
  // Perform Reachability Check
73
75
  const results = await this.performReachabilityCheck(clusters);
@@ -75,9 +77,15 @@ export default class Reachability {
75
77
  // @ts-ignore
76
78
  await this.webex.boundedStorage.put(
77
79
  this.namespace,
78
- REACHABILITY.localStorage,
80
+ REACHABILITY.localStorageResult,
79
81
  JSON.stringify(results)
80
82
  );
83
+ // @ts-ignore
84
+ await this.webex.boundedStorage.put(
85
+ this.namespace,
86
+ REACHABILITY.localStorageJoinCookie,
87
+ JSON.stringify(joinCookie)
88
+ );
81
89
 
82
90
  LoggerProxy.logger.log(
83
91
  'Reachability:index#gatherReachability --> Reachability checks completed'
@@ -103,7 +111,7 @@ export default class Reachability {
103
111
  let reachable = false;
104
112
  // @ts-ignore
105
113
  const reachabilityData = await this.webex.boundedStorage
106
- .get(this.namespace, REACHABILITY.localStorage)
114
+ .get(this.namespace, REACHABILITY.localStorageResult)
107
115
  .catch(() => {});
108
116
 
109
117
  if (reachabilityData) {
@@ -28,21 +28,23 @@ class ReachabilityRequest {
28
28
  }
29
29
 
30
30
  /**
31
- * gets the cluster information
31
+ * Gets the cluster information
32
32
  *
33
- * @param {boolean} includeVideoMesh whether to include the video mesh clusters in the result or not
34
33
  * @returns {Promise}
35
34
  */
36
- getClusters = (): Promise<ClusterList> =>
35
+ getClusters = (): Promise<{clusters: ClusterList; joinCookie: any}> =>
37
36
  this.webex
38
37
  .request({
39
38
  method: HTTP_VERBS.GET,
40
39
  shouldRefreshAccessToken: false,
41
40
  api: API.CALLIOPEDISCOVERY,
42
41
  resource: RESOURCE.CLUSTERS,
42
+ qs: {
43
+ JCSupport: 1,
44
+ },
43
45
  })
44
46
  .then((res) => {
45
- const {clusters} = res.body;
47
+ const {clusters, joinCookie} = res.body;
46
48
 
47
49
  Object.keys(clusters).forEach((key) => {
48
50
  clusters[key].isVideoMesh = res.body.clusterClasses?.hybridMedia?.includes(key);
@@ -52,7 +54,10 @@ class ReachabilityRequest {
52
54
  `Reachability:request#getClusters --> get clusters successful:${JSON.stringify(clusters)}`
53
55
  );
54
56
 
55
- return clusters;
57
+ return {
58
+ clusters,
59
+ joinCookie,
60
+ };
56
61
  });
57
62
 
58
63
  /**
@@ -12,14 +12,16 @@ import {eventType} from '../metrics/config';
12
12
  */
13
13
  export default class RoapRequest extends StatelessWebexPlugin {
14
14
  /**
15
- * Joins a meeting via ROAP
15
+ * Returns reachability data.
16
16
  * @param {Object} localSdp
17
- * @returns {Promise} returns a promise that resolves/rejects whatever the request does
17
+ * @returns {Object}
18
18
  */
19
- async attachRechabilityData(localSdp) {
19
+ async attachReachabilityData(localSdp) {
20
+ let joinCookie;
21
+
20
22
  // @ts-ignore
21
23
  const reachabilityData = await this.webex.boundedStorage
22
- .get(REACHABILITY.namespace, REACHABILITY.localStorage)
24
+ .get(REACHABILITY.namespace, REACHABILITY.localStorageResult)
23
25
  .catch(() => {});
24
26
 
25
27
  if (reachabilityData) {
@@ -37,7 +39,22 @@ export default class RoapRequest extends StatelessWebexPlugin {
37
39
  }
38
40
  }
39
41
 
40
- return localSdp;
42
+ // @ts-ignore
43
+ const joinCookieRaw = await this.webex.boundedStorage
44
+ .get(REACHABILITY.namespace, REACHABILITY.localStorageJoinCookie)
45
+ .catch(() => {});
46
+
47
+ if (joinCookieRaw) {
48
+ try {
49
+ joinCookie = JSON.parse(joinCookieRaw);
50
+ } catch (e) {
51
+ LoggerProxy.logger.error(
52
+ `MeetingRequest#constructor --> Error in parsing join cookie data: ${e}`
53
+ );
54
+ }
55
+ }
56
+
57
+ return {localSdp, joinCookie};
41
58
  }
42
59
 
43
60
  /**
@@ -53,7 +70,7 @@ export default class RoapRequest extends StatelessWebexPlugin {
53
70
  * @param {Boolean} options.preferTranscoding
54
71
  * @returns {Promise} returns the response/failure of the request
55
72
  */
56
- sendRoap(options: {
73
+ async sendRoap(options: {
57
74
  roapMessage: any;
58
75
  locusSelfUrl: string;
59
76
  mediaId: string;
@@ -69,6 +86,14 @@ export default class RoapRequest extends StatelessWebexPlugin {
69
86
  LoggerProxy.logger.info('Roap:request#sendRoap --> Race Condition /call mediaID not present');
70
87
  }
71
88
 
89
+ const {localSdp: localSdpWithReachabilityData, joinCookie} = await this.attachReachabilityData({
90
+ roapMessage,
91
+ // eslint-disable-next-line no-warning-comments
92
+ // TODO: check whats the need for video and audiomute
93
+ audioMuted: !!options.audioMuted,
94
+ videoMuted: !!options.videoMuted,
95
+ });
96
+
72
97
  const mediaUrl = `${locusSelfUrl}/${MEDIA}`;
73
98
  // @ts-ignore
74
99
  const deviceUrl = this.webex.internal.device.url;
@@ -79,79 +104,69 @@ export default class RoapRequest extends StatelessWebexPlugin {
79
104
 
80
105
  Metrics.postEvent({event: eventType.MEDIA_REQUEST, meetingId});
81
106
 
82
- return this.attachRechabilityData({
83
- roapMessage,
84
- // eslint-disable-next-line no-warning-comments
85
- // TODO: check whats the need for video and audiomute
86
- audioMuted: !!options.audioMuted,
87
- videoMuted: !!options.videoMuted,
88
- }).then((sdpWithReachability) => {
89
- // @ts-ignore
90
- return this.webex
91
- .request({
92
- uri: mediaUrl,
93
- method: HTTP_VERBS.PUT,
94
- body: {
95
- device: {
96
- url: deviceUrl,
97
- // @ts-ignore
98
- deviceType: this.config.meetings.deviceType,
99
- },
100
- correlationId,
101
- localMedias: [
102
- {
103
- localSdp: JSON.stringify(sdpWithReachability),
104
- mediaId: options.mediaId,
105
- },
106
- ],
107
- clientMediaPreferences: {
108
- preferTranscoding: options.preferTranscoding ?? true,
109
- },
107
+ // @ts-ignore
108
+ return this.request({
109
+ uri: mediaUrl,
110
+ method: HTTP_VERBS.PUT,
111
+ body: {
112
+ device: {
113
+ url: deviceUrl,
114
+ // @ts-ignore
115
+ deviceType: this.config.meetings.deviceType,
116
+ },
117
+ correlationId,
118
+ localMedias: [
119
+ {
120
+ localSdp: JSON.stringify(localSdpWithReachabilityData),
121
+ mediaId: options.mediaId,
110
122
  },
111
- })
112
- .then((res) => {
113
- Metrics.postEvent({event: eventType.MEDIA_RESPONSE, meetingId});
114
-
115
- // always it will be the first mediaConnection Object
116
- const mediaConnections =
117
- res.body.mediaConnections &&
118
- res.body.mediaConnections.length > 0 &&
119
- res.body.mediaConnections[0];
120
-
121
- LoggerProxy.logger.debug(
122
- `Roap:request#sendRoap --> response:${JSON.stringify(
123
- mediaConnections,
124
- null,
125
- 2
126
- )}'\n StatusCode:'${res.statusCode}`
127
- );
128
- const {locus} = res.body;
129
-
130
- locus.roapSeq = options.roapMessage.seq;
131
-
132
- return {
133
- locus,
134
- ...(mediaConnections && {mediaConnections: res.body.mediaConnections}),
135
- };
136
- })
137
- .catch((err) => {
138
- Metrics.postEvent({
139
- event: eventType.MEDIA_RESPONSE,
140
- meetingId,
141
- data: {error: Metrics.parseLocusError(err, true)},
142
- });
143
- LoggerProxy.logger.error(
144
- `Roap:request#sendRoap --> Error:${JSON.stringify(err, null, 2)}`
145
- );
146
- LoggerProxy.logger.error(
147
- `Roap:request#sendRoapRequest --> errorBody:${JSON.stringify(
148
- roapMessage,
149
- null,
150
- 2
151
- )} + '\\n mediaId:'${options.mediaId}`
152
- );
153
- throw err;
123
+ ],
124
+ clientMediaPreferences: {
125
+ preferTranscoding: options.preferTranscoding ?? true,
126
+ joinCookie,
127
+ },
128
+ },
129
+ })
130
+ .then((res) => {
131
+ Metrics.postEvent({event: eventType.MEDIA_RESPONSE, meetingId});
132
+
133
+ // always it will be the first mediaConnection Object
134
+ const mediaConnections =
135
+ res.body.mediaConnections &&
136
+ res.body.mediaConnections.length > 0 &&
137
+ res.body.mediaConnections[0];
138
+
139
+ LoggerProxy.logger.debug(
140
+ `Roap:request#sendRoap --> response:${JSON.stringify(
141
+ mediaConnections,
142
+ null,
143
+ 2
144
+ )}'\n StatusCode:'${res.statusCode}`
145
+ );
146
+ const {locus} = res.body;
147
+
148
+ locus.roapSeq = options.roapMessage.seq;
149
+
150
+ return {
151
+ locus,
152
+ ...(mediaConnections && {mediaConnections: res.body.mediaConnections}),
153
+ };
154
+ })
155
+ .catch((err) => {
156
+ Metrics.postEvent({
157
+ event: eventType.MEDIA_RESPONSE,
158
+ meetingId,
159
+ data: {error: Metrics.parseLocusError(err, true)},
154
160
  });
155
- });
161
+ LoggerProxy.logger.error(`Roap:request#sendRoap --> Error:${JSON.stringify(err, null, 2)}`);
162
+ LoggerProxy.logger.error(
163
+ `Roap:request#sendRoapRequest --> errorBody:${JSON.stringify(
164
+ roapMessage,
165
+ null,
166
+ 2
167
+ )} + '\\n mediaId:'${options.mediaId}`
168
+ );
169
+ throw err;
170
+ });
156
171
  }
157
172
  }
@@ -26,6 +26,8 @@ describe('plugin-meetings', () => {
26
26
  },
27
27
  };
28
28
 
29
+ webex.boundedStorage.get = sinon.mock().returns(Promise.resolve(JSON.stringify({anycastEntryPoint: "aws-eu-west-1"})))
30
+
29
31
  meetingsRequest = new MeetingRequest(
30
32
  {},
31
33
  {
@@ -206,6 +208,31 @@ describe('plugin-meetings', () => {
206
208
  const requestParams = meetingsRequest.request.getCall(0).args[0];
207
209
 
208
210
  assert.deepEqual(requestParams.body.deviceCapabilities, undefined);
211
+
212
+ });
213
+
214
+ it('includes joinCookie correctly', async () => {
215
+ const locusUrl = 'locusURL';
216
+ const deviceUrl = 'deviceUrl';
217
+ const correlationId = 'random-uuid';
218
+ const roapMessage = 'roap-message';
219
+ const permissionToken = 'permission-token';
220
+
221
+ await meetingsRequest.joinMeeting({
222
+ locusUrl,
223
+ deviceUrl,
224
+ correlationId,
225
+ roapMessage,
226
+ permissionToken,
227
+ });
228
+ const requestParams = meetingsRequest.request.getCall(0).args[0];
229
+
230
+ assert.equal(requestParams.method, 'POST');
231
+ assert.equal(requestParams.uri, `${locusUrl}/participant?alternateRedirect=true`);
232
+ assert.deepEqual(requestParams.body.clientMediaPreferences, {
233
+ "joinCookie": {anycastEntryPoint: "aws-eu-west-1"},
234
+ "preferTranscoding": true
235
+ });
209
236
  });
210
237
  });
211
238
 
@@ -22,8 +22,8 @@ describe('isAnyClusterReachable', () => {
22
22
 
23
23
  const result = await reachability.isAnyClusterReachable();
24
24
 
25
- assert.equal(result, expectedValue);
26
- };
25
+ assert.equal(result, expectedValue);
26
+ };
27
27
 
28
28
  it('returns true when udp is reachable', async () => {
29
29
  await checkIsClusterReachable({x: {udp: {reachable: 'true'}, tcp: {reachable: 'false'}}}, true);
@@ -66,19 +66,30 @@ describe('gatherReachability', () => {
66
66
  it('stores the reachability', async () => {
67
67
  const reachability = new Reachability(webex);
68
68
 
69
- const clusters = {some: 'clusters'};
70
- const reachabilityResults = {some: 'results'};
69
+ const reachabilityResults = {
70
+ clusters: {
71
+ clusterId: {
72
+ udp: 'testUDP',
73
+ },
74
+ },
75
+ }
76
+ const getClustersResult = {
77
+ clusters: {clusterId: 'cluster'},
78
+ joinCookie: {id: 'id'}
79
+ };
71
80
 
72
- reachability.reachabilityRequest.getClusters = sinon.stub().returns(clusters);
81
+ reachability.reachabilityRequest.getClusters = sinon.stub().returns(getClustersResult);
73
82
  (reachability as any).performReachabilityCheck = sinon.stub().returns(reachabilityResults)
74
83
 
75
84
  const result = await reachability.gatherReachability();
76
85
 
77
86
  assert.equal(result, reachabilityResults);
78
87
 
79
- const storedResult = await webex.boundedStorage.get('Reachability', 'reachability.result');
88
+ const storedResultForReachabilityResult = await webex.boundedStorage.get('Reachability', 'reachability.result');
89
+ const storedResultForJoinCookie = await webex.boundedStorage.get('Reachability', 'reachability.joinCookie');
80
90
 
81
- assert.equal(JSON.stringify(result), storedResult);
91
+ assert.equal(JSON.stringify(result), storedResultForReachabilityResult);
92
+ assert.equal(JSON.stringify(getClustersResult.joinCookie), storedResultForJoinCookie);
82
93
  });
83
94
 
84
95
  });
@@ -0,0 +1,66 @@
1
+ import sinon from 'sinon';
2
+ import {assert} from '@webex/test-helper-chai';
3
+ import MockWebex from '@webex/test-helper-mock-webex';
4
+ import Meetings from '@webex/plugin-meetings';
5
+ import ReachabilityRequest from '@webex/plugin-meetings/src/reachability/request';
6
+
7
+
8
+ describe('plugin-meetings/reachability', () => {
9
+ let reachabilityRequest;
10
+ let webex;
11
+
12
+ beforeEach(() => {
13
+ webex = new MockWebex({
14
+ children: {
15
+ meetings: Meetings,
16
+ },
17
+ });
18
+
19
+ webex.meetings.clientRegion = {
20
+ countryCode: 'US',
21
+ regionCode: 'WEST-COAST',
22
+ };
23
+
24
+ webex.internal = {
25
+ services: {
26
+ get: sinon.mock().returns('locusUrl'),
27
+ waitForCatalog: sinon.mock().returns(Promise.resolve({})),
28
+ },
29
+ };
30
+
31
+
32
+ reachabilityRequest = new ReachabilityRequest(webex);
33
+
34
+ });
35
+
36
+ describe('#getClusters', () => {
37
+ it('sends a GET request with the correct params', async () => {
38
+ webex.request = sinon.mock().returns(Promise.resolve({
39
+ body: {
40
+ clusterClasses: {
41
+ hybridMedia: ["clusterId"]
42
+ },
43
+ clusters: {"clusterId": {
44
+ udp: "testUDP"
45
+ }},
46
+ joinCookie: {anycastEntryPoint: "aws-eu-west-1"}
47
+ }
48
+ }));
49
+
50
+ const res = await reachabilityRequest.getClusters();
51
+
52
+ const requestParams = webex.request.getCall(0).args[0];
53
+
54
+ assert.equal(requestParams.method, 'GET');
55
+ assert.equal(requestParams.resource, `clusters`);
56
+ assert.equal(requestParams.api, 'calliopeDiscovery');
57
+ assert.equal(requestParams.shouldRefreshAccessToken, false);
58
+
59
+ assert.deepEqual(requestParams.qs, {
60
+ JCSupport: 1,
61
+ });
62
+ assert.deepEqual(res.clusters.clusterId, {udp: "testUDP", isVideoMesh: true})
63
+ assert.deepEqual(res.joinCookie, {anycastEntryPoint: "aws-eu-west-1"})
64
+ });
65
+ });
66
+ });