@webex/plugin-meetings 1.145.0 → 1.147.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/config.js +1 -0
  2. package/dist/config.js.map +1 -1
  3. package/dist/constants.js +4 -2
  4. package/dist/constants.js.map +1 -1
  5. package/dist/media/index.js +16 -8
  6. package/dist/media/index.js.map +1 -1
  7. package/dist/meeting/index.js +19 -2
  8. package/dist/meeting/index.js.map +1 -1
  9. package/dist/meeting/util.js +2 -1
  10. package/dist/meeting/util.js.map +1 -1
  11. package/dist/members/index.js +63 -0
  12. package/dist/members/index.js.map +1 -1
  13. package/dist/members/request.js +41 -0
  14. package/dist/members/request.js.map +1 -1
  15. package/dist/members/util.js +59 -0
  16. package/dist/members/util.js.map +1 -1
  17. package/dist/metrics/index.js +0 -2
  18. package/dist/metrics/index.js.map +1 -1
  19. package/dist/peer-connection-manager/index.js +8 -7
  20. package/dist/peer-connection-manager/index.js.map +1 -1
  21. package/dist/reconnection-manager/index.js +2 -1
  22. package/dist/reconnection-manager/index.js.map +1 -1
  23. package/dist/roap/handler.js +7 -8
  24. package/dist/roap/handler.js.map +1 -1
  25. package/dist/roap/index.js +8 -3
  26. package/dist/roap/index.js.map +1 -1
  27. package/dist/roap/state.js +2 -0
  28. package/dist/roap/state.js.map +1 -1
  29. package/package.json +5 -5
  30. package/src/config.js +1 -0
  31. package/src/constants.js +1 -0
  32. package/src/media/index.js +58 -10
  33. package/src/meeting/index.js +17 -2
  34. package/src/meeting/util.js +2 -1
  35. package/src/members/index.js +56 -0
  36. package/src/members/request.js +35 -0
  37. package/src/members/util.js +58 -1
  38. package/src/metrics/index.js +0 -2
  39. package/src/peer-connection-manager/index.js +13 -9
  40. package/src/reconnection-manager/index.js +2 -1
  41. package/src/roap/handler.js +7 -8
  42. package/src/roap/index.js +10 -3
  43. package/src/roap/state.js +2 -0
  44. package/test/unit/spec/meeting/index.js +16 -0
  45. package/test/unit/spec/members/index.js +192 -0
  46. package/test/unit/spec/members/request.js +101 -0
@@ -71,13 +71,23 @@ Media.setLocalTrack = (enabled, track) => {
71
71
  * @param {string} meetingProperties.remoteQualityLevel LOW|MEDIUM|HIGH
72
72
  * @returns {Promise}
73
73
  */
74
- Media.reconnectMedia = (peerConnection, {meetingId, remoteQualityLevel, enableRtx}) => {
74
+ Media.reconnectMedia = (peerConnection, {
75
+ meetingId,
76
+ remoteQualityLevel,
77
+ enableRtx,
78
+ enableExtmap
79
+ }) => {
75
80
  if (peerConnection.connectionState === PEER_CONNECTION_STATE.CLOSED ||
76
81
  peerConnection.connectionState === PEER_CONNECTION_STATE.FAILED) {
77
82
  return Promise.reject(new ReconnectionError('Reinitiate peerconnection'));
78
83
  }
79
84
 
80
- return PeerConnectionManager.createOffer(peerConnection, {meetingId, remoteQualityLevel, enableRtx});
85
+ return PeerConnectionManager.createOffer(peerConnection, {
86
+ meetingId,
87
+ remoteQualityLevel,
88
+ enableRtx,
89
+ enableExtmap
90
+ });
81
91
  };
82
92
 
83
93
  /**
@@ -165,9 +175,18 @@ Media.checkTracks = (trackType, track, receiveTracks) => {
165
175
  * @param {string} meetingProperties.remoteQualityLevel LOW|MEDIUM|HIGH
166
176
  * @returns {Array} [peerConnection, ]
167
177
  */
168
- Media.attachMedia = (mediaProperties, {meetingId, remoteQualityLevel, enableRtx}) => {
178
+ Media.attachMedia = (mediaProperties, {
179
+ meetingId,
180
+ remoteQualityLevel,
181
+ enableRtx,
182
+ enableExtmap
183
+ }) => {
169
184
  const {
170
- mediaDirection, audioTrack, videoTrack, shareTrack, peerConnection
185
+ mediaDirection,
186
+ audioTrack,
187
+ videoTrack,
188
+ shareTrack,
189
+ peerConnection
171
190
  } = mediaProperties;
172
191
 
173
192
  let result = null;
@@ -188,7 +207,12 @@ Media.attachMedia = (mediaProperties, {meetingId, remoteQualityLevel, enableRtx}
188
207
  LoggerProxy.logger.info(`Media:index#attachMedia --> onnegotiationneeded#PeerConnection: ${event}`);
189
208
  };
190
209
 
191
- return PeerConnectionManager.createOffer(peerConnection, {meetingId, remoteQualityLevel, enableRtx});
210
+ return PeerConnectionManager.createOffer(peerConnection, {
211
+ meetingId,
212
+ remoteQualityLevel,
213
+ enableRtx,
214
+ enableExtmap
215
+ });
192
216
  };
193
217
 
194
218
  /**
@@ -199,9 +223,18 @@ Media.attachMedia = (mediaProperties, {meetingId, remoteQualityLevel, enableRtx}
199
223
  * @param {string} meetingProperties.remoteQualityLevel LOW|MEDIUM|HIGH
200
224
  * @returns {Promise}
201
225
  */
202
- Media.updateMedia = (mediaProperties, {meetingId, remoteQualityLevel, enableRtx}) => {
226
+ Media.updateMedia = (mediaProperties, {
227
+ meetingId,
228
+ remoteQualityLevel,
229
+ enableRtx,
230
+ enableExtmap
231
+ }) => {
203
232
  const {
204
- mediaDirection, audioTrack, videoTrack, shareTrack, peerConnection
233
+ mediaDirection,
234
+ audioTrack,
235
+ videoTrack,
236
+ shareTrack,
237
+ peerConnection
205
238
  } = mediaProperties;
206
239
 
207
240
  // update audio transceiver
@@ -231,7 +264,12 @@ Media.updateMedia = (mediaProperties, {meetingId, remoteQualityLevel, enableRtx}
231
264
  LoggerProxy.logger.info(`Media:index#updateMedia --> onnegotiationneeded#PeerConnection: ${event}`);
232
265
  };
233
266
 
234
- return PeerConnectionManager.createOffer(peerConnection, {meetingId, remoteQualityLevel, enableRtx});
267
+ return PeerConnectionManager.createOffer(peerConnection, {
268
+ meetingId,
269
+ remoteQualityLevel,
270
+ enableRtx,
271
+ enableExtmap
272
+ });
235
273
  };
236
274
 
237
275
  /**
@@ -269,10 +307,20 @@ Media.setTrackOnTransceiver = (transceiver, options) => {
269
307
  * @param {Object} options see #Media.setTrackOnTransceiver
270
308
  * @returns {Promise}
271
309
  */
272
- Media.updateTransceiver = ({meetingId, remoteQualityLevel, enableRtx}, peerConnection, transceiver, options) => {
310
+ Media.updateTransceiver = ({
311
+ meetingId,
312
+ remoteQualityLevel,
313
+ enableRtx,
314
+ enableExtmap
315
+ }, peerConnection, transceiver, options) => {
273
316
  Media.setTrackOnTransceiver(transceiver, options);
274
317
 
275
- return PeerConnectionManager.createOffer(peerConnection, {meetingId, remoteQualityLevel, enableRtx});
318
+ return PeerConnectionManager.createOffer(peerConnection, {
319
+ meetingId,
320
+ remoteQualityLevel,
321
+ enableRtx,
322
+ enableExtmap
323
+ });
276
324
  };
277
325
 
278
326
  /**
@@ -1742,6 +1742,7 @@ export default class Meeting extends StatelessWebexPlugin {
1742
1742
  * @param {Object} invitee
1743
1743
  * @param {String} invitee.emailAddress
1744
1744
  * @param {String} invitee.email
1745
+ * @param {String} invitee.phoneNumber
1745
1746
  * @param {Boolean} [alertIfActive]
1746
1747
  * @returns {Promise} see #members.addMember
1747
1748
  * @public
@@ -1751,6 +1752,18 @@ export default class Meeting extends StatelessWebexPlugin {
1751
1752
  return this.members.addMember(invitee, alertIfActive);
1752
1753
  }
1753
1754
 
1755
+ /**
1756
+ * Cancel an outgoing phone call invitation made during a meeting
1757
+ * @param {Object} invitee
1758
+ * @param {String} invitee.phoneNumber
1759
+ * @returns {Promise} see #members.cancelPhoneInvite
1760
+ * @public
1761
+ * @memberof Meeting
1762
+ */
1763
+ cancelPhoneInvite(invitee) {
1764
+ return this.members.cancelPhoneInvite(invitee);
1765
+ }
1766
+
1754
1767
  /**
1755
1768
  * Admit the guest(s) to the call once they are waiting
1756
1769
  * @param {Array} memberIds
@@ -3663,7 +3676,8 @@ export default class Meeting extends StatelessWebexPlugin {
3663
3676
  .then(() => Media.attachMedia(this.mediaProperties, {
3664
3677
  meetingId: this.id,
3665
3678
  remoteQualityLevel: this.mediaProperties.remoteQualityLevel,
3666
- enableRtx: this.config.enableRtx
3679
+ enableRtx: this.config.enableRtx,
3680
+ enableExtmap: this.config.enableExtmap
3667
3681
  })
3668
3682
  .then((peerConnection) => this.getDevices().then((devices) => {
3669
3683
  MeetingUtil.handleDeviceLogging(devices);
@@ -3937,7 +3951,8 @@ export default class Meeting extends StatelessWebexPlugin {
3937
3951
  .then(() => Media.updateMedia(this.mediaProperties, {
3938
3952
  meetingId: this.id,
3939
3953
  remoteQualityLevel: this.mediaProperties.remoteQualityLevel,
3940
- enableRtx: this.config.enableRtx
3954
+ enableRtx: this.config.enableRtx,
3955
+ enableExtmap: this.config.enableExtmap
3941
3956
  })
3942
3957
  .then((peerConnection) => {
3943
3958
  LoggerProxy.logger.info(`${LOG_HEADER} PeerConnection received from updateMedia, ${peerConnection}`);
@@ -302,7 +302,8 @@ MeetingUtil.updateTransceiver = (options, meetingOptions) => {
302
302
  return Media.updateTransceiver({
303
303
  meetingId: meetingOptions.meeting.id,
304
304
  remoteQualityLevel: meetingOptions.mediaProperties.remoteQualityLevel,
305
- enableRtx: meetingOptions.meeting.config.enableRtx
305
+ enableRtx: meetingOptions.meeting.config.enableRtx,
306
+ enableExtmap: meetingOptions.meeting.config.enableExtmap
306
307
  }, peerConnection, transceiver,
307
308
  {
308
309
  track,
@@ -594,6 +594,27 @@ export default class Members extends StatelessWebexPlugin {
594
594
  return this.membersRequest.addMembers(options);
595
595
  }
596
596
 
597
+ /**
598
+ * Cancels an outgoing PSTN call to the associated meeting
599
+ * @param {String} invitee
600
+ * @returns {Promise}
601
+ * @memberof Members
602
+ */
603
+ cancelPhoneInvite(invitee) {
604
+ if (!this.locusUrl) {
605
+ return Promise.reject(new ParameterError('The associated locus url for this meeting object must be defined.'));
606
+ }
607
+ if (MembersUtil.isInvalidInvitee(invitee)) {
608
+ return Promise.reject(
609
+ new ParameterError('The invitee must be defined with a valid phoneNumber property.')
610
+ );
611
+ }
612
+ const options = MembersUtil.cancelPhoneInviteOptions(invitee, this.locusUrl);
613
+
614
+
615
+ return this.membersRequest.cancelPhoneInvite(options);
616
+ }
617
+
597
618
  /**
598
619
  * Admits waiting members (invited guests to meeting)
599
620
  * @param {Array} memberIds
@@ -668,4 +689,39 @@ export default class Members extends StatelessWebexPlugin {
668
689
 
669
690
  return this.membersRequest.transferHostToMember(options);
670
691
  }
692
+
693
+
694
+ /**
695
+ * Sends DTMF tones for the PSTN member of a meeting
696
+ * @param {String} tones a string of one or more DTMF tones to send
697
+ * @param {String} memberId member id
698
+ * @returns {Promise}
699
+ * @public
700
+ * @memberof Members
701
+ */
702
+ sendDialPadKey(tones = '', memberId = '') {
703
+ if (!tones && tones !== 0) {
704
+ return Promise.reject(new ParameterError('DMTF tones must be passed in'));
705
+ }
706
+
707
+ const member = this.membersCollection.get(memberId);
708
+
709
+ if (!member) {
710
+ return Promise.reject(new ParameterError('there is no member associated with that Id'));
711
+ }
712
+
713
+ const {locusUrl} = this;
714
+
715
+ const deviceArray = member.participant.devices;
716
+ const device = deviceArray.find(({deviceType}) => deviceType === 'SIP');
717
+ const url = device?.url;
718
+
719
+ if (locusUrl && url) {
720
+ const options = MembersUtil.genderateSendDTMFOptions(url, tones, memberId, locusUrl);
721
+
722
+ return this.membersRequest.sendDialPadKey(options);
723
+ }
724
+
725
+ return Promise.reject(new Error('Members:index#sendDialPadKey --> cannot send DTMF, meeting does not have a connection to the "locus" call control service.'));
726
+ }
671
727
  }
@@ -73,4 +73,39 @@ export default class MembersRequest extends StatelessWebexPlugin {
73
73
 
74
74
  return this.request(requestParams);
75
75
  }
76
+
77
+ /**
78
+ * Sends a request to the DTMF endpoint to send tones
79
+ * @param {Object} options
80
+ * @param {String} options.locusUrl
81
+ * @param {String} options.url device url SIP user
82
+ * @param {String} options.tones a string of one or more DTMF tones to send
83
+ * @param {String} options.memberId ID of PSTN user
84
+ * @returns {Promise}
85
+ */
86
+ sendDialPadKey(options) {
87
+ if (!options || !options.locusUrl || !options.memberId || !options.url || !options.tones && options.tones !== 0) {
88
+ throw new ParameterError('memberId must be defined, the associated locus url, the device url and DTMF tones for this meeting object must be defined.');
89
+ }
90
+
91
+ const requestParams = MembersUtil.generateSendDTMFRequestParams(options);
92
+
93
+ return this.request(requestParams);
94
+ }
95
+
96
+ /**
97
+ * @param {Object} options with format of {invitee: string, locusUrl: string}
98
+ * @returns {Promise}
99
+ * @throws {Error} if the options are not valid and complete, must have invitee with emailAddress OR email AND locusUrl
100
+ * @memberof MembersRequest
101
+ */
102
+ cancelPhoneInvite(options) {
103
+ if (!(options?.invitee?.phoneNumber || options?.locusUrl)) {
104
+ throw new ParameterError('invitee must be passed and the associated locus url for this meeting object must be defined.');
105
+ }
106
+
107
+ const requestParams = MembersUtil.generateCancelInviteRequestParams(options);
108
+
109
+ return this.request(requestParams);
110
+ }
76
111
  }
@@ -1,3 +1,5 @@
1
+ import uuid from 'uuid';
2
+
1
3
  import {
2
4
  HTTP_VERBS,
3
5
  CONTROLS,
@@ -5,7 +7,9 @@ import {
5
7
  LEAVE,
6
8
  PARTICIPANT,
7
9
  VALID_EMAIL_ADDRESS,
8
- DIALER_REGEX
10
+ DIALER_REGEX,
11
+ SEND_DTMF_ENDPOINT,
12
+ _REMOVE_
9
13
  } from '../constants';
10
14
 
11
15
  const MembersUtil = {};
@@ -156,4 +160,57 @@ MembersUtil.getTransferHostToMemberRequestParams = (options) => {
156
160
  };
157
161
  };
158
162
 
163
+ MembersUtil.genderateSendDTMFOptions = (url, tones, memberId, locusUrl) => ({
164
+ url,
165
+ tones,
166
+ memberId,
167
+ locusUrl
168
+ });
169
+
170
+ MembersUtil.generateSendDTMFRequestParams = ({
171
+ url, tones, memberId, locusUrl
172
+ }) => {
173
+ const body = {
174
+ device: {
175
+ url
176
+ },
177
+ memberId,
178
+ dtmf: {
179
+ correlationId: uuid.v4(),
180
+ tones,
181
+ direction: 'transmit'
182
+ }
183
+ };
184
+ const uri = `${locusUrl}/${PARTICIPANT}/${memberId}/${SEND_DTMF_ENDPOINT}`;
185
+
186
+ return {
187
+ method: HTTP_VERBS.POST,
188
+ uri,
189
+ body
190
+ };
191
+ };
192
+
193
+ MembersUtil.cancelPhoneInviteOptions = (invitee, locusUrl) => ({
194
+ invitee,
195
+ locusUrl
196
+ });
197
+
198
+ MembersUtil.generateCancelInviteRequestParams = (options) => {
199
+ const body = {
200
+ actionType: _REMOVE_,
201
+ invitees: [
202
+ {
203
+ address: options.invitee.phoneNumber
204
+ }
205
+ ]
206
+ };
207
+ const requestParams = {
208
+ method: HTTP_VERBS.PUT,
209
+ uri: options.locusUrl,
210
+ body
211
+ };
212
+
213
+ return requestParams;
214
+ };
215
+
159
216
  export default MembersUtil;
@@ -175,7 +175,6 @@ class Metrics {
175
175
  eventId: uuid.v4(),
176
176
  version: 1,
177
177
  origin: {
178
- buildType: 'prod',
179
178
  name: 'endpoint',
180
179
  networkType: 'unknown',
181
180
  userAgent: this.userAgentToString(),
@@ -264,7 +263,6 @@ class Metrics {
264
263
  origin: {
265
264
  audioSetupDelay,
266
265
  videoSetupDelay,
267
- buildType: 'prod',
268
266
  name: 'endpoint',
269
267
  networkType: options.networkType || UNKNOWN,
270
268
  userAgent: this.userAgentToString(),
@@ -186,7 +186,6 @@ pc.iceCandidate = (peerConnection, {remoteQualityLevel}) =>
186
186
  const timeout = setTimeout(() => {
187
187
  peerConnection.sdp = limitBandwidth(peerConnection.localDescription.sdp);
188
188
  peerConnection.sdp = setMaxFs(peerConnection.sdp, remoteQualityLevel);
189
- peerConnection.sdp = peerConnection.sdp.replace(/\na=extmap.*/g, '');
190
189
 
191
190
  if (isSdpInvalid(peerConnection.sdp)) {
192
191
  setTimeout(() => {
@@ -214,7 +213,6 @@ pc.iceCandidate = (peerConnection, {remoteQualityLevel}) =>
214
213
  if (!evt.candidate && !peerConnection.sdp) {
215
214
  peerConnection.sdp = limitBandwidth(peerConnection.localDescription.sdp);
216
215
  peerConnection.sdp = setMaxFs(peerConnection.sdp, remoteQualityLevel);
217
- peerConnection.sdp = peerConnection.sdp.replace(/\na=extmap.*/g, '');
218
216
 
219
217
  if (evt.candidate === null && !isSdpInvalid(peerConnection.sdp)) {
220
218
  clearTimeout(timeout);
@@ -292,9 +290,7 @@ pc.addStream = (peerConnection, stream) => {
292
290
  */
293
291
  pc.setRemoteSessionDetails = (peerConnection, typeStr, remoteSdp, meetingId) => {
294
292
  LoggerProxy.logger.log(`PeerConnectionManager:index#setRemoteSessionDetails --> Setting the remote description type: ${typeStr}State: ${peerConnection.signalingState}`);
295
- let sdp = remoteSdp;
296
-
297
- sdp = sdp.replace(/\na=extmap.*/g, '');
293
+ const sdp = remoteSdp;
298
294
 
299
295
  // making sure that the remoteDescription is only set when there is a answer for offer
300
296
  // or there is a offer from the server
@@ -363,9 +359,15 @@ pc.setRemoteSessionDetails = (peerConnection, typeStr, remoteSdp, meetingId) =>
363
359
  * @param {string} meetingProperties.meetingId
364
360
  * @param {string} meetingProperties.remoteQualityLevel LOW|MEDIUM|HIGH
365
361
  * @param {string} meetingProperties.enableRtx
362
+ * @param {string} meetingProperties.enableExtmap
366
363
  * @returns {RTCPeerConnection}
367
364
  */
368
- pc.createOffer = (peerConnection, {meetingId, remoteQualityLevel, enableRtx}) => {
365
+ pc.createOffer = (peerConnection, {
366
+ meetingId,
367
+ remoteQualityLevel,
368
+ enableRtx,
369
+ enableExtmap
370
+ }) => {
369
371
  LoggerProxy.logger.log('PeerConnectionManager:index#createOffer --> creating a new offer');
370
372
 
371
373
  return peerConnection
@@ -389,7 +391,11 @@ pc.createOffer = (peerConnection, {meetingId, remoteQualityLevel, enableRtx}) =>
389
391
  if (!checkH264Support(peerConnection.sdp)) {
390
392
  throw new MediaError('openH264 is downloading please Wait. Upload logs if not working on second try');
391
393
  }
392
- peerConnection.sdp = peerConnection.sdp.replace(/\na=extmap.*/g, '');
394
+
395
+ if (!enableExtmap) {
396
+ peerConnection.sdp = peerConnection.sdp.replace(/\na=extmap.*/g, '');
397
+ }
398
+
393
399
  pc.setContentSlides(peerConnection);
394
400
 
395
401
  Metrics.postEvent({
@@ -512,8 +518,6 @@ pc.createAnswer = (params, {meetingId, remoteQualityLevel}) => {
512
518
  throw new MediaError('openH264 is downloading please Wait. Upload logs if not working on second try');
513
519
  }
514
520
 
515
- peerConnection.sdp = peerConnection.sdp.replace(/\na=extmap.*/g, '');
516
-
517
521
  return peerConnection;
518
522
  })
519
523
  .catch((error) => {
@@ -459,7 +459,8 @@ export default class ReconnectionManager {
459
459
  return Media.attachMedia(this.meeting.mediaProperties, {
460
460
  meetingId: this.meeting.id,
461
461
  remoteQualityLevel: this.meeting.mediaProperties.remoteQualityLevel,
462
- enableRtx: this.meeting.config.enableRtx
462
+ enableRtx: this.meeting.config.enableRtx,
463
+ enableExtmap: this.meeting.config.enableExtmap
463
464
  })
464
465
  .then((peerConnection) => this.meeting.setRemoteStream(peerConnection))
465
466
  .then(() => {
@@ -192,8 +192,10 @@ export default class RoapHandler extends StatelessWebexPlugin {
192
192
  handleAction(session, action, meeting, correlationId) {
193
193
  let signal;
194
194
 
195
+
195
196
  switch (action.type) {
196
197
  case ROAP.RECEIVE_ROAP_MSG:
198
+ LoggerProxy.logger.log(`Roap:handler#handleAction --> RECEIVE_ROAP_MSG event captured, reciving a roap message : ${JSON.stringify(action)}`);
197
199
  if (compareWithLastRoapMessage(this.lastRoapMessage, action)) {
198
200
  LoggerProxy.logger.warn(`Roap:handler#handleAction --> duplicate roap offer from server: ${action.msg.seq}`);
199
201
  }
@@ -204,18 +206,15 @@ export default class RoapHandler extends StatelessWebexPlugin {
204
206
  }
205
207
  break;
206
208
  case ROAP.SEND_ROAP_MSG:
209
+ LoggerProxy.logger.log(`Roap:handler#handleAction --> SEND_ROAP_MSG event captured, sending roap message ${JSON.stringify(action)}`);
210
+
207
211
  action.local = true;
208
212
  this.execute(signal, session, action, meeting, ROAP.TX_);
209
213
  break;
210
214
  case ROAP.SEND_ROAP_MSG_SUCCESS:
211
- // This means we got and answer and waiting for 200 ok for /participants
212
- if (RoapCollection.getSessionSequence(correlationId, action.seq).ANSWER) {
213
- signal = ROAP.ROAP_SIGNAL.RX_ANSWER;
214
- // NOTE: When server send back an answer via mercury the
215
- // remote SDP is already saved sent and ok message is sent back
216
- // We dont have to indicate the roapHandler about the RX_ANSWER via SEND_ROAP_MSG_SUCCESS
217
- // RoapHandler.transition(signal, session, meeting);
218
- }
215
+ // NOTE: When server send back an answer via mercury the
216
+ // remote SDP is already saved sent and ok message is sent back
217
+ // We dont have to indicate the roapHandler about the RX_ANSWER via SEND_ROAP_MSG_SUCCESS
219
218
  break;
220
219
  case ROAP.RECEIVE_CALL_LEAVE:
221
220
  RoapCollection.deleteSession(correlationId);
package/src/roap/index.js CHANGED
@@ -174,6 +174,12 @@ export default class Roap extends StatelessWebexPlugin {
174
174
  seq: options.seq
175
175
  };
176
176
 
177
+ this.roapHandler.submit({
178
+ type: ROAP.SEND_ROAP_MSG,
179
+ msg: roapMessage,
180
+ correlationId: options.correlationId
181
+ });
182
+
177
183
  return this.roapRequest
178
184
  .sendRoap({
179
185
  roapMessage,
@@ -186,10 +192,11 @@ export default class Roap extends StatelessWebexPlugin {
186
192
  })
187
193
  .then(() => {
188
194
  meeting.setRoapSeq(options.seq);
195
+
189
196
  this.roapHandler.submit({
190
- type: ROAP.SEND_ROAP_MSG,
191
- msg: roapMessage,
192
- correlationId: options.correlationId
197
+ type: ROAP.SEND_ROAP_MSG_SUCCESS,
198
+ seq: roapMessage.seq,
199
+ correlationId: meeting.correlationId
193
200
  });
194
201
  });
195
202
  }
package/src/roap/state.js CHANGED
@@ -25,6 +25,8 @@ const shouldStep = (roap, meeting) => {
25
25
  };
26
26
 
27
27
  const handleTransition = (value, signal, meeting) => {
28
+ LoggerProxy.logger.log(`Roap:state#handleTransition --> current ${value} to ${signal}`);
29
+
28
30
  switch (value) {
29
31
  case ROAP.ROAP_STATE.INIT:
30
32
  if (signal === ROAP.ROAP_SIGNAL.RX_OFFER) {
@@ -186,6 +186,22 @@ describe('plugin-meetings', () => {
186
186
  assert.calledWith(meeting.members.addMember, uuid1, false);
187
187
  });
188
188
  });
189
+ describe('#cancelPhoneInvite', () => {
190
+ it('should have #invite', () => {
191
+ assert.exists(meeting.cancelPhoneInvite);
192
+ });
193
+ beforeEach(() => {
194
+ meeting.members.cancelPhoneInvite = sinon.stub().returns(Promise.resolve(test1));
195
+ });
196
+ it('should proxy members #cancelPhoneInvite and return a promise', async () => {
197
+ const cancel = meeting.cancelPhoneInvite(uuid1);
198
+
199
+ assert.exists(cancel.then);
200
+ await cancel;
201
+ assert.calledOnce(meeting.members.cancelPhoneInvite);
202
+ assert.calledWith(meeting.members.cancelPhoneInvite, uuid1);
203
+ });
204
+ });
189
205
  describe('#admit', () => {
190
206
  it('should have #admit', () => {
191
207
  assert.exists(meeting.admit);