@webex/plugin-meetings 2.25.0 → 2.26.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 (47) hide show
  1. package/dist/common/logs/logger-proxy.js +7 -6
  2. package/dist/common/logs/logger-proxy.js.map +1 -1
  3. package/dist/config.js +2 -1
  4. package/dist/config.js.map +1 -1
  5. package/dist/constants.js +4 -11
  6. package/dist/constants.js.map +1 -1
  7. package/dist/media/properties.js +2 -2
  8. package/dist/media/properties.js.map +1 -1
  9. package/dist/media/util.js +18 -4
  10. package/dist/media/util.js.map +1 -1
  11. package/dist/meeting/index.js +3 -1
  12. package/dist/meeting/index.js.map +1 -1
  13. package/dist/metrics/constants.js +2 -1
  14. package/dist/metrics/constants.js.map +1 -1
  15. package/dist/peer-connection-manager/index.js +6 -4
  16. package/dist/peer-connection-manager/index.js.map +1 -1
  17. package/dist/reconnection-manager/index.js +40 -14
  18. package/dist/reconnection-manager/index.js.map +1 -1
  19. package/dist/roap/handler.js +0 -2
  20. package/dist/roap/handler.js.map +1 -1
  21. package/dist/roap/index.js +36 -7
  22. package/dist/roap/index.js.map +1 -1
  23. package/dist/roap/request.js +5 -3
  24. package/dist/roap/request.js.map +1 -1
  25. package/dist/roap/turnDiscovery.js +273 -0
  26. package/dist/roap/turnDiscovery.js.map +1 -0
  27. package/dist/roap/util.js +0 -2
  28. package/dist/roap/util.js.map +1 -1
  29. package/package.json +5 -5
  30. package/src/common/logs/logger-proxy.js +7 -6
  31. package/src/config.js +2 -1
  32. package/src/constants.ts +3 -4
  33. package/src/media/properties.js +2 -2
  34. package/src/media/util.js +17 -7
  35. package/src/meeting/index.js +3 -2
  36. package/src/metrics/constants.js +2 -1
  37. package/src/peer-connection-manager/index.js +6 -3
  38. package/src/reconnection-manager/index.js +13 -10
  39. package/src/roap/handler.js +0 -2
  40. package/src/roap/index.js +36 -6
  41. package/src/roap/request.js +5 -3
  42. package/src/roap/turnDiscovery.ts +238 -0
  43. package/src/roap/util.js +0 -2
  44. package/test/unit/spec/meeting/index.js +35 -0
  45. package/test/unit/spec/peerconnection-manager/index.js +47 -4
  46. package/test/unit/spec/roap/index.ts +113 -0
  47. package/test/unit/spec/roap/turnDiscovery.ts +334 -0
@@ -0,0 +1 @@
1
+ {"version":3,"names":["TURN_DISCOVERY_TIMEOUT","TurnDiscovery","roapRequest","turnInfo","url","username","password","defer","LoggerProxy","logger","warn","reject","Error","responseTimer","setTimeout","info","promise","roapMessage","headers","expectedHeaders","headerName","field","foundHeaders","forEach","receivedHeader","expectedHeader","startsWith","substring","length","clearTimeout","undefined","resolve","meeting","isReconnecting","seq","roapSeq","Defer","messageType","ROAP","ROAP_TYPES","TURN_DISCOVERY_REQUEST","version","ROAP_VERSION","sendRoap","correlationId","locusSelfUrl","selfUrl","mediaId","audioMuted","isAudioMuted","videoMuted","isVideoMuted","meetingId","id","then","mediaConnections","setRoapSeq","updateMediaConnections","OK","config","experimental","enableTurnDiscovery","sendRoapTurnDiscoveryRequest","waitForTurnDiscoveryResponse","sendRoapOK","catch","e","Metrics","sendBehavioralMetric","BEHAVIORAL_METRICS","TURN_DISCOVERY_FAILURE","correlation_id","locus_id","locusUrl","split","pop","reason","message","stack"],"sources":["turnDiscovery.ts"],"sourcesContent":["import {Defer} from '@webex/common';\n\nimport Metrics from '../metrics';\nimport BEHAVIORAL_METRICS from '../metrics/constants';\nimport LoggerProxy from '../common/logs/logger-proxy';\nimport {ROAP} from '../constants';\n\nimport RoapRequest from './request';\n\nconst TURN_DISCOVERY_TIMEOUT = 10; // in seconds\n\n/**\n * Handles the process of finding out TURN server information from Linus.\n * This is achieved by sending a TURN_DISCOVERY_REQUEST.\n */\nexport default class TurnDiscovery {\n private roapRequest: RoapRequest;\n\n private defer?: Defer; // used for waiting for the response\n\n private turnInfo: {\n url: string;\n username: string;\n password: string;\n };\n\n private responseTimer?: ReturnType<typeof setTimeout>;\n\n /**\n * Constructor\n *\n * @param {RoapRequest} roapRequest\n */\n constructor(roapRequest: RoapRequest) {\n this.roapRequest = roapRequest;\n this.turnInfo = {\n url: '',\n username: '',\n password: '',\n };\n }\n\n\n /**\n * waits for TURN_DISCOVERY_RESPONSE message to arrive\n *\n * @returns {Promise}\n * @private\n * @memberof Roap\n */\n waitForTurnDiscoveryResponse() {\n if (!this.defer) {\n LoggerProxy.logger.warn('Roap:turnDiscovery#waitForTurnDiscoveryResponse --> TURN discovery is not in progress');\n\n return Promise.reject(new Error('waitForTurnDiscoveryResponse() called before sendRoapTurnDiscoveryRequest()'));\n }\n\n const {defer} = this;\n\n this.responseTimer = setTimeout(() => {\n LoggerProxy.logger.warn(`Roap:turnDiscovery#waitForTurnDiscoveryResponse --> timeout! no response arrived within ${TURN_DISCOVERY_TIMEOUT} seconds`);\n\n defer.reject(new Error('Timed out waiting for TURN_DISCOVERY_RESPONSE'));\n }, TURN_DISCOVERY_TIMEOUT * 1000);\n\n LoggerProxy.logger.info('Roap:turnDiscovery#waitForTurnDiscoveryResponse --> waiting for TURN_DISCOVERY_RESPONSE...');\n\n return defer.promise;\n }\n\n /**\n * handles TURN_DISCOVERY_RESPONSE roap message\n *\n * @param {Object} roapMessage\n * @returns {void}\n * @public\n * @memberof Roap\n */\n handleTurnDiscoveryResponse(roapMessage) {\n const {headers} = roapMessage;\n\n if (!this.defer) {\n LoggerProxy.logger.warn('Roap:turnDiscovery#handleTurnDiscoveryResponse --> unexpected TURN discovery response');\n\n return;\n }\n\n const expectedHeaders = [\n {headerName: 'x-cisco-turn-url', field: 'url'},\n {headerName: 'x-cisco-turn-username', field: 'username'},\n {headerName: 'x-cisco-turn-password', field: 'password'},\n ];\n\n let foundHeaders = 0;\n\n headers?.forEach((receivedHeader) => {\n // check if it matches any of our expected headers\n expectedHeaders.forEach((expectedHeader) => {\n if (receivedHeader.startsWith(`${expectedHeader.headerName}=`)) {\n this.turnInfo[expectedHeader.field] = receivedHeader.substring(expectedHeader.headerName.length + 1);\n foundHeaders += 1;\n }\n });\n });\n\n clearTimeout(this.responseTimer);\n this.responseTimer = undefined;\n\n if (foundHeaders !== expectedHeaders.length) {\n LoggerProxy.logger.warn(`Roap:turnDiscovery#handleTurnDiscoveryResponse --> missing some headers, received: ${JSON.stringify(headers)}`);\n this.defer.reject(new Error(`TURN_DISCOVERY_RESPONSE missing some headers: ${JSON.stringify(headers)}`));\n }\n else {\n LoggerProxy.logger.info(`Roap:turnDiscovery#handleTurnDiscoveryResponse --> received a valid response, url=${this.turnInfo.url}`);\n this.defer.resolve();\n }\n }\n\n /**\n * sends the TURN_DISCOVERY_REQUEST roap request\n *\n * @param {Meeting} meeting\n * @param {Boolean} isReconnecting\n * @returns {Promise}\n * @private\n * @memberof Roap\n */\n sendRoapTurnDiscoveryRequest(meeting, isReconnecting) {\n const seq = meeting.roapSeq + 1;\n\n if (this.defer) {\n LoggerProxy.logger.warn('Roap:turnDiscovery#sendRoapTurnDiscoveryRequest --> already in progress');\n\n return Promise.resolve();\n }\n\n this.defer = new Defer();\n\n const roapMessage = {\n messageType: ROAP.ROAP_TYPES.TURN_DISCOVERY_REQUEST,\n version: ROAP.ROAP_VERSION,\n seq,\n };\n\n LoggerProxy.logger.info('Roap:turnDiscovery#sendRoapTurnDiscoveryRequest --> sending TURN_DISCOVERY_REQUEST');\n\n return this.roapRequest\n .sendRoap({\n roapMessage,\n correlationId: meeting.correlationId,\n locusSelfUrl: meeting.selfUrl,\n mediaId: isReconnecting ? '' : meeting.mediaId,\n audioMuted: meeting.isAudioMuted(),\n videoMuted: meeting.isVideoMuted(),\n meetingId: meeting.id\n })\n .then(({mediaConnections}) => {\n meeting.setRoapSeq(seq);\n\n if (mediaConnections) {\n meeting.updateMediaConnections(mediaConnections);\n }\n });\n }\n\n /**\n * Sends the OK message that server expects to receive\n * after it sends us TURN_DISCOVERY_RESPONSE\n *\n * @param {Meeting} meeting\n * @returns {Promise}\n */\n sendRoapOK(meeting) {\n LoggerProxy.logger.info('Roap:turnDiscovery#sendRoapOK --> sending OK');\n\n return this.roapRequest.sendRoap({\n roapMessage: {\n messageType: ROAP.ROAP_TYPES.OK,\n version: ROAP.ROAP_VERSION,\n seq: meeting.roapSeq\n },\n locusSelfUrl: meeting.selfUrl,\n mediaId: meeting.mediaId,\n correlationId: meeting.correlationId,\n audioMuted: meeting.isAudioMuted(),\n videoMuted: meeting.isVideoMuted(),\n meetingId: meeting.id\n });\n }\n\n /**\n * Retrieves TURN server information from the backend by doing\n * a roap message exchange:\n * client server\n * | -----TURN_DISCOVERY_REQUEST-----> |\n * | <----TURN_DISCOVERY_RESPONSE----- |\n * | --------------OK----------------> |\n *\n * @param {Meeting} meeting\n * @param {Boolean} isReconnecting should be set to true if this is a new\n * media connection just after a reconnection\n * @returns {Promise}\n */\n doTurnDiscovery(meeting, isReconnecting) {\n if (!meeting.config.experimental.enableTurnDiscovery) {\n LoggerProxy.logger.info('Roap:turnDiscovery#doTurnDiscovery --> TURN discovery disabled in config, skipping it');\n\n return Promise.resolve(undefined);\n }\n\n return this.sendRoapTurnDiscoveryRequest(meeting, isReconnecting)\n .then(() => this.waitForTurnDiscoveryResponse())\n .then(() => this.sendRoapOK(meeting))\n .then(() => {\n this.defer = undefined;\n\n LoggerProxy.logger.info('Roap:turnDiscovery#doTurnDiscovery --> TURN discovery completed');\n\n return this.turnInfo;\n })\n .catch((e) => {\n // we catch any errors and resolve with no turn information so that the normal call join flow can continue without TURN\n LoggerProxy.logger.info(`Roap:turnDiscovery#doTurnDiscovery --> TURN discovery failed, continuing without TURN: ${e}`);\n\n Metrics.sendBehavioralMetric(\n BEHAVIORAL_METRICS.TURN_DISCOVERY_FAILURE,\n {\n correlation_id: meeting.correlationId,\n locus_id: meeting.locusUrl.split('/').pop(),\n reason: e.message,\n stack: e.stack\n }\n );\n\n return Promise.resolve(undefined);\n });\n }\n}\n"],"mappings":"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"}
package/dist/roap/util.js CHANGED
@@ -87,8 +87,6 @@ RoapUtil.setRemoteDescription = function (meeting, session) {
87
87
 
88
88
  return {
89
89
  seq: session.ANSWER.seq,
90
- locusId: meeting.locusId,
91
- locusSelfId: meeting.locusInfo.self.id,
92
90
  mediaId: meeting.mediaId,
93
91
  correlationId: meeting.correlationId
94
92
  };
@@ -1 +1 @@
1
- {"version":3,"names":["RoapUtil","ROAP_ANSWER","_ANSWER_","toLowerCase","shouldHandleMedia","meeting","offer","mediaProperties","peerConnection","signalingState","SDP","HAVE_LOCAL_OFFER","handleError","pc","PeerConnectionManager","rollBackLocalDescription","then","resolve","catch","err","LoggerProxy","logger","error","reject","findError","messageType","errorType","type","ROAP","RECEIVE_ROAP_MSG","SEND_ROAP_MSG","_ERROR_","_CONFLICT_","ensureMeeting","SEND_ROAP_MSG_SUCCESS","updatePeerConnection","session","offerSdp","OFFER","sdps","meetingId","id","remoteQualityLevel","res","roap","lastRoapOffer","setRemoteDescription","info","correlationId","ParameterError","setRemoteSessionDetails","ANSWER","seq","locusId","locusSelfId","locusInfo","self","mediaId"],"sources":["util.js"],"sourcesContent":["\nimport PeerConnectionManager from '../peer-connection-manager';\nimport {\n _ANSWER_,\n _ERROR_,\n _CONFLICT_,\n ROAP,\n SDP\n} from '../constants';\nimport LoggerProxy from '../common/logs/logger-proxy';\nimport ParameterError from '../common/errors/parameter';\n\nconst RoapUtil = {};\nconst ROAP_ANSWER = _ANSWER_.toLowerCase();\n\nRoapUtil.shouldHandleMedia = (meeting) => {\n const offer =\n meeting.mediaProperties.peerConnection &&\n meeting.mediaProperties.peerConnection.signalingState === SDP.HAVE_LOCAL_OFFER;\n\n if (offer) {\n return false;\n }\n\n return true;\n};\n\nRoapUtil.handleError = (pc) =>\n PeerConnectionManager.rollBackLocalDescription({peerConnection: pc})\n .then(() => Promise.resolve(true))\n .catch((err) => {\n LoggerProxy.logger.error(`Roap:util#handleError --> ${err}`);\n\n return Promise.reject(err);\n });\n\nRoapUtil.findError = (messageType, errorType, type) =>\n (type === ROAP.RECEIVE_ROAP_MSG || type === ROAP.SEND_ROAP_MSG) && messageType === _ERROR_ && errorType === _CONFLICT_;\n\nRoapUtil.ensureMeeting = (meeting, type) => {\n if (type === ROAP.RECEIVE_ROAP_MSG || type === ROAP.SEND_ROAP_MSG || type === ROAP.SEND_ROAP_MSG_SUCCESS) {\n if (!meeting) {\n return false;\n }\n }\n\n return true;\n};\n\nRoapUtil.updatePeerConnection = (meeting, session) => PeerConnectionManager.updatePeerConnection({\n offerSdp: session.OFFER.sdps,\n peerConnection: meeting.mediaProperties.peerConnection\n},\n{\n meetingId: meeting.id,\n remoteQualityLevel: meeting.mediaProperties.remoteQualityLevel\n})\n .then((res) => {\n meeting.roap.lastRoapOffer = session.OFFER.sdps;\n\n return res;\n });\n\n\nRoapUtil.setRemoteDescription = (meeting, session) => {\n LoggerProxy.logger.info(`Roap:util#setRemoteDescription --> Transmit WAIT_TX_OK, correlationId: ${meeting.correlationId}`);\n if (!(meeting && (meeting.mediaProperties.peerConnection))) {\n LoggerProxy.logger.error(`Roap:util#setRemoteDescription --> DANGER no media or screen peer connection, correlationId: ${meeting.correlationId}`);\n\n return Promise.reject(new ParameterError('Must provide a media or screen peer connection'));\n }\n\n return PeerConnectionManager.setRemoteSessionDetails(\n meeting.mediaProperties.peerConnection,\n ROAP_ANSWER,\n session.ANSWER.sdps[0],\n meeting.id\n ).then(() => {\n LoggerProxy.logger.info(`Roap:util#setRemoteDescription --> Success for correlationId: ${meeting.correlationId}`);\n\n return {\n seq: session.ANSWER.seq,\n locusId: meeting.locusId,\n locusSelfId: meeting.locusInfo.self.id,\n mediaId: meeting.mediaId,\n correlationId: meeting.correlationId\n };\n })\n .catch((err) => {\n LoggerProxy.logger.error(`Roap:util#setRemoteDescription --> ${err}`);\n throw err;\n });\n};\n\nexport default RoapUtil;\n"],"mappings":"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"}
1
+ {"version":3,"names":["RoapUtil","ROAP_ANSWER","_ANSWER_","toLowerCase","shouldHandleMedia","meeting","offer","mediaProperties","peerConnection","signalingState","SDP","HAVE_LOCAL_OFFER","handleError","pc","PeerConnectionManager","rollBackLocalDescription","then","resolve","catch","err","LoggerProxy","logger","error","reject","findError","messageType","errorType","type","ROAP","RECEIVE_ROAP_MSG","SEND_ROAP_MSG","_ERROR_","_CONFLICT_","ensureMeeting","SEND_ROAP_MSG_SUCCESS","updatePeerConnection","session","offerSdp","OFFER","sdps","meetingId","id","remoteQualityLevel","res","roap","lastRoapOffer","setRemoteDescription","info","correlationId","ParameterError","setRemoteSessionDetails","ANSWER","seq","mediaId"],"sources":["util.js"],"sourcesContent":["\nimport PeerConnectionManager from '../peer-connection-manager';\nimport {\n _ANSWER_,\n _ERROR_,\n _CONFLICT_,\n ROAP,\n SDP\n} from '../constants';\nimport LoggerProxy from '../common/logs/logger-proxy';\nimport ParameterError from '../common/errors/parameter';\n\nconst RoapUtil = {};\nconst ROAP_ANSWER = _ANSWER_.toLowerCase();\n\nRoapUtil.shouldHandleMedia = (meeting) => {\n const offer =\n meeting.mediaProperties.peerConnection &&\n meeting.mediaProperties.peerConnection.signalingState === SDP.HAVE_LOCAL_OFFER;\n\n if (offer) {\n return false;\n }\n\n return true;\n};\n\nRoapUtil.handleError = (pc) =>\n PeerConnectionManager.rollBackLocalDescription({peerConnection: pc})\n .then(() => Promise.resolve(true))\n .catch((err) => {\n LoggerProxy.logger.error(`Roap:util#handleError --> ${err}`);\n\n return Promise.reject(err);\n });\n\nRoapUtil.findError = (messageType, errorType, type) =>\n (type === ROAP.RECEIVE_ROAP_MSG || type === ROAP.SEND_ROAP_MSG) && messageType === _ERROR_ && errorType === _CONFLICT_;\n\nRoapUtil.ensureMeeting = (meeting, type) => {\n if (type === ROAP.RECEIVE_ROAP_MSG || type === ROAP.SEND_ROAP_MSG || type === ROAP.SEND_ROAP_MSG_SUCCESS) {\n if (!meeting) {\n return false;\n }\n }\n\n return true;\n};\n\nRoapUtil.updatePeerConnection = (meeting, session) => PeerConnectionManager.updatePeerConnection({\n offerSdp: session.OFFER.sdps,\n peerConnection: meeting.mediaProperties.peerConnection\n},\n{\n meetingId: meeting.id,\n remoteQualityLevel: meeting.mediaProperties.remoteQualityLevel\n})\n .then((res) => {\n meeting.roap.lastRoapOffer = session.OFFER.sdps;\n\n return res;\n });\n\n\nRoapUtil.setRemoteDescription = (meeting, session) => {\n LoggerProxy.logger.info(`Roap:util#setRemoteDescription --> Transmit WAIT_TX_OK, correlationId: ${meeting.correlationId}`);\n if (!(meeting && (meeting.mediaProperties.peerConnection))) {\n LoggerProxy.logger.error(`Roap:util#setRemoteDescription --> DANGER no media or screen peer connection, correlationId: ${meeting.correlationId}`);\n\n return Promise.reject(new ParameterError('Must provide a media or screen peer connection'));\n }\n\n return PeerConnectionManager.setRemoteSessionDetails(\n meeting.mediaProperties.peerConnection,\n ROAP_ANSWER,\n session.ANSWER.sdps[0],\n meeting.id\n ).then(() => {\n LoggerProxy.logger.info(`Roap:util#setRemoteDescription --> Success for correlationId: ${meeting.correlationId}`);\n\n return {\n seq: session.ANSWER.seq,\n mediaId: meeting.mediaId,\n correlationId: meeting.correlationId\n };\n })\n .catch((err) => {\n LoggerProxy.logger.error(`Roap:util#setRemoteDescription --> ${err}`);\n throw err;\n });\n};\n\nexport default RoapUtil;\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webex/plugin-meetings",
3
- "version": "2.25.0",
3
+ "version": "2.26.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.25.0",
28
- "@webex/internal-plugin-mercury": "2.25.0",
29
- "@webex/internal-plugin-conversation": "2.25.0",
27
+ "@webex/webex-core": "2.26.0",
28
+ "@webex/internal-plugin-mercury": "2.26.0",
29
+ "@webex/internal-plugin-conversation": "2.26.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.25.0",
35
+ "@webex/common": "2.26.0",
36
36
  "bowser": "^2.11.0",
37
37
  "sdp-transform": "^2.12.0",
38
38
  "btoa": "^1.2.1",
@@ -1,13 +1,14 @@
1
+ /* eslint-disable no-unused-vars */
1
2
  import LoggerConfig from './logger-config';
2
3
 
3
4
  const LoggerProxy = {
4
5
  logger: {
5
- info: () => { console.error('LoggerProxy->info#NO LOGGER DEFINED'); },
6
- log: () => { console.error('LoggerProxy->log#NO LOGGER DEFINED'); },
7
- error: () => { console.error('LoggerProxy->error#NO LOGGER DEFINED'); },
8
- warn: () => { console.error('LoggerProxy->warn#NO LOGGER DEFINED'); },
9
- trace: () => { console.error('LoggerProxy->trace#NO LOGGER DEFINED'); },
10
- debug: () => { console.error('LoggerProxy->debug#NO LOGGER DEFINED'); }
6
+ info: (args) => { console.error('LoggerProxy->info#NO LOGGER DEFINED'); },
7
+ log: (args) => { console.error('LoggerProxy->log#NO LOGGER DEFINED'); },
8
+ error: (args) => { console.error('LoggerProxy->error#NO LOGGER DEFINED'); },
9
+ warn: (args) => { console.error('LoggerProxy->warn#NO LOGGER DEFINED'); },
10
+ trace: (args) => { console.error('LoggerProxy->trace#NO LOGGER DEFINED'); },
11
+ debug: (args) => { console.error('LoggerProxy->debug#NO LOGGER DEFINED'); }
11
12
  }
12
13
  };
13
14
 
package/src/config.js CHANGED
@@ -90,7 +90,8 @@ export default {
90
90
  experimental: {
91
91
  enableMediaNegotiatedEvent: false,
92
92
  enableUnifiedMeetings: false,
93
- enableAdhocMeetings: false
93
+ enableAdhocMeetings: false,
94
+ enableTurnDiscovery: false,
94
95
  }
95
96
  }
96
97
  };
package/src/constants.ts CHANGED
@@ -762,9 +762,6 @@ export const PEER_CONNECTION_STATE = {
762
762
  FAILED: 'failed'
763
763
  };
764
764
 
765
- export const RTC_CONFIGURATION_FIREFOX = {iceServers: [], bundlePolicy: 'max-compat'};
766
- export const RTC_CONFIGURATION = {iceServers: []};
767
-
768
765
  export const RECONNECTION = {
769
766
  STATE: {
770
767
  IN_PROGRESS: 'IN_PROGRESS',
@@ -795,7 +792,9 @@ export const ROAP = {
795
792
  OK: 'OK',
796
793
  ERROR: 'ERROR',
797
794
  SHUTDOWN: 'SHUTDOWN',
798
- OFFER_REQUEST: 'OFFER_REQUEST'
795
+ OFFER_REQUEST: 'OFFER_REQUEST',
796
+ TURN_DISCOVERY_REQUEST: 'TURN_DISCOVERY_REQUEST',
797
+ TURN_DISCOVERY_RESPONSE: 'TURN_DISCOVERY_RESPONSE',
799
798
  },
800
799
  ROAP_STATE: {
801
800
  INIT: 'INIT',
@@ -106,8 +106,8 @@ export default class MediaProperties {
106
106
  this.peerConnection = null;
107
107
  }
108
108
 
109
- reInitiatePeerconnection() {
110
- this.peerConnection = MediaUtil.createPeerConnection();
109
+ reInitiatePeerconnection(turnServerInfo) {
110
+ this.peerConnection = MediaUtil.createPeerConnection(turnServerInfo);
111
111
  }
112
112
 
113
113
  unsetLocalVideoTrack() {
package/src/media/util.js CHANGED
@@ -2,19 +2,29 @@
2
2
  import window from 'global/window';
3
3
 
4
4
  import BrowserDetection from '../common/browser-detection';
5
- import {
6
- RTC_CONFIGURATION,
7
- RTC_CONFIGURATION_FIREFOX
8
- } from '../constants';
9
5
  import LoggerProxy from '../common/logs/logger-proxy';
10
6
 
11
7
  const {isBrowser} = BrowserDetection();
12
8
 
13
9
  const MediaUtil = {};
14
10
 
15
- MediaUtil.createPeerConnection = () => new window.RTCPeerConnection(
16
- isBrowser('firefox') ? RTC_CONFIGURATION_FIREFOX : RTC_CONFIGURATION
17
- );
11
+ MediaUtil.createPeerConnection = (turnServerInfo) => {
12
+ const config = {iceServers: []};
13
+
14
+ if (turnServerInfo) {
15
+ config.iceServers.push({
16
+ urls: turnServerInfo.url,
17
+ username: turnServerInfo.username || '',
18
+ credential: turnServerInfo.password || ''
19
+ });
20
+ }
21
+ if (isBrowser('firefox')) {
22
+ config.bundlePolicy = 'max-compat';
23
+ }
24
+
25
+ return new window.RTCPeerConnection(config);
26
+ };
27
+
18
28
 
19
29
  MediaUtil.createMediaStream = (tracks) => {
20
30
  if (!tracks) {
@@ -4132,8 +4132,9 @@ export default class Meeting extends StatelessWebexPlugin {
4132
4132
  });
4133
4133
 
4134
4134
  return MeetingUtil.validateOptions(options)
4135
- .then(() => {
4136
- this.mediaProperties.setMediaPeerConnection(MediaUtil.createPeerConnection());
4135
+ .then(() => this.roap.doTurnDiscovery(this, false))
4136
+ .then((turnServerInfo) => {
4137
+ this.mediaProperties.setMediaPeerConnection(MediaUtil.createPeerConnection(turnServerInfo));
4137
4138
  this.setMercuryListener();
4138
4139
  PeerConnectionManager.setPeerConnectionEvents(this);
4139
4140
 
@@ -55,7 +55,8 @@ const BEHAVIORAL_METRICS = {
55
55
  MOVE_TO_SUCCESS: 'js_sdk_move_to_success',
56
56
  MOVE_TO_FAILURE: 'js_sdk_move_to_failure',
57
57
  MOVE_FROM_SUCCESS: 'js_sdk_move_from_success',
58
- MOVE_FROM_FAILURE: 'js_sdk_move_from_failure'
58
+ MOVE_FROM_FAILURE: 'js_sdk_move_from_failure',
59
+ TURN_DISCOVERY_FAILURE: 'js_sdk_turn_discovery_failure',
59
60
  };
60
61
 
61
62
 
@@ -26,7 +26,7 @@ import BEHAVIORAL_METRICS from '../metrics/constants';
26
26
  import {error, eventType} from '../metrics/config';
27
27
  import MediaError from '../common/errors/media';
28
28
  import ParameterError from '../common/errors/parameter';
29
- import {InvalidSdpError, IceGatheringFailed} from '../common/errors/webex-errors';
29
+ import {InvalidSdpError} from '../common/errors/webex-errors';
30
30
  import BrowserDetection from '../common/browser-detection';
31
31
 
32
32
  import PeerConnectionUtils from './util';
@@ -227,8 +227,9 @@ pc.iceCandidate = (peerConnection, {remoteQualityLevel}) =>
227
227
  };
228
228
 
229
229
  peerConnection.onicecandidateerror = (event) => {
230
- LoggerProxy.logger.error('PeerConnectionManager:index#onicecandidateerror --> Failed to gather ice candidate.', event);
231
- reject(new IceGatheringFailed());
230
+ // we can often get ICE candidate errors (for example when failing to communicate with a TURN server)
231
+ // they don't mean that the whole ICE connection will fail, so it's OK to ignore them
232
+ LoggerProxy.logger.error('PeerConnectionManager:index#onicecandidateerror --> ignoring ice error:', event);
232
233
  };
233
234
  });
234
235
 
@@ -328,6 +329,8 @@ pc.setRemoteSessionDetails = (
328
329
  sdp = sdp.replace(/\na=extmap.*/g, '');
329
330
  }
330
331
 
332
+ // remove any xtls candidates
333
+ sdp = sdp.replace(/^a=candidate:.*xTLS.*\r\n/gim, '');
331
334
 
332
335
  return peerConnection.setRemoteDescription(
333
336
  new window.RTCSessionDescription({
@@ -454,14 +454,13 @@ export default class ReconnectionManager {
454
454
  reconnectMedia() {
455
455
  LoggerProxy.logger.log('ReconnectionManager:index#reconnectMedia --> Begin reestablishment of media');
456
456
 
457
- ReconnectionManager.setupPeerConnection(this.meeting);
458
-
459
- return Media.attachMedia(this.meeting.mediaProperties, {
460
- meetingId: this.meeting.id,
461
- remoteQualityLevel: this.meeting.mediaProperties.remoteQualityLevel,
462
- enableRtx: this.meeting.config.enableRtx,
463
- enableExtmap: this.meeting.config.enableExtmap
464
- })
457
+ return ReconnectionManager.setupPeerConnection(this.meeting)
458
+ .then(() => Media.attachMedia(this.meeting.mediaProperties, {
459
+ meetingId: this.meeting.id,
460
+ remoteQualityLevel: this.meeting.mediaProperties.remoteQualityLevel,
461
+ enableRtx: this.meeting.config.enableRtx,
462
+ enableExtmap: this.meeting.config.enableExtmap
463
+ }))
465
464
  .then((peerConnection) => this.meeting.setRemoteStream(peerConnection))
466
465
  .then(() => {
467
466
  LoggerProxy.logger.log('ReconnectionManager:index#reconnectMedia --> Sending ROAP media request');
@@ -516,13 +515,17 @@ export default class ReconnectionManager {
516
515
  * @private
517
516
  * @memberof ReconnectionManager
518
517
  */
519
- static setupPeerConnection(meeting) {
518
+ static async setupPeerConnection(meeting) {
520
519
  LoggerProxy.logger.log('ReconnectionManager:index#setupPeerConnection --> Begin resetting peer connection');
521
520
  // close pcs, unset to null and create a new one with out closing any streams
522
521
  PeerConnectionManager.close(meeting.mediaProperties.peerConnection);
523
522
  meeting.mediaProperties.unsetPeerConnection();
524
- meeting.mediaProperties.reInitiatePeerconnection();
523
+
524
+ const turnInfo = await meeting.roap.doTurnDiscovery(meeting, true);
525
+
526
+ meeting.mediaProperties.reInitiatePeerconnection(turnInfo);
525
527
  PeerConnectionManager.setPeerConnectionEvents(meeting);
528
+
526
529
  // update the peerconnection in the stats manager when ever we reconnect
527
530
  meeting.statsAnalyzer.updatePeerconnection(meeting.mediaProperties.peerConnection);
528
531
  }
@@ -102,8 +102,6 @@ export default class RoapHandler extends StatelessWebexPlugin {
102
102
  RoapUtil.updatePeerConnection(meeting, session)
103
103
  .then((answerSdps) => {
104
104
  this.roapAnswer({
105
- locusId: meeting.locusId,
106
- locusSelfId: meeting.locusInfo.self.id,
107
105
  mediaId: meeting.mediaId,
108
106
  sdps: answerSdps,
109
107
  seq: session.OFFER.seq,
package/src/roap/index.js CHANGED
@@ -7,6 +7,7 @@ import MeetingUtil from '../meeting/util';
7
7
  import RoapHandler from './handler';
8
8
  import RoapRequest from './request';
9
9
  import RoapCollection from './collection';
10
+ import TurnDiscovery from './turnDiscovery';
10
11
 
11
12
  /**
12
13
  * Roap options
@@ -75,6 +76,8 @@ export default class Roap extends StatelessWebexPlugin {
75
76
  * @memberof Roap
76
77
  */
77
78
  this.lastRoapOffer = {};
79
+
80
+ this.turnDiscovery = new TurnDiscovery(this.roapRequest);
78
81
  }
79
82
 
80
83
  /**
@@ -89,11 +92,19 @@ export default class Roap extends StatelessWebexPlugin {
89
92
  const {correlationId} = data;
90
93
 
91
94
  LoggerProxy.logger.log(`Roap:index#roapEvent --> Received Roap Message [${JSON.stringify(msg, null, 2)}]`);
92
- this.roapHandler.submit({
93
- type: ROAP.RECEIVE_ROAP_MSG,
94
- msg,
95
- correlationId
96
- });
95
+
96
+ if (msg.messageType === ROAP.ROAP_TYPES.TURN_DISCOVERY_RESPONSE) {
97
+ // turn discovery is not part of normal roap protocol and so we are not handling it
98
+ // through the usual roap state machine
99
+ this.turnDiscovery.handleTurnDiscoveryResponse(msg);
100
+ }
101
+ else {
102
+ this.roapHandler.submit({
103
+ type: ROAP.RECEIVE_ROAP_MSG,
104
+ msg,
105
+ correlationId
106
+ });
107
+ }
97
108
  }
98
109
 
99
110
  /**
@@ -245,12 +256,17 @@ export default class Roap extends StatelessWebexPlugin {
245
256
  correlationId: meeting.correlationId
246
257
  });
247
258
 
259
+ // When reconnecting, it's important that the first roap message being sent out has empty media id.
260
+ // Normally this is the roap offer, but when TURN discovery is enabled,
261
+ // then this is the TURN discovery request message
262
+ const sendEmptyMediaId = reconnect && !meeting.config.experimental.enableTurnDiscovery;
263
+
248
264
  return this.roapRequest
249
265
  .sendRoap({
250
266
  roapMessage,
251
267
  correlationId: meeting.correlationId,
252
268
  locusSelfUrl: meeting.selfUrl,
253
- mediaId: reconnect ? '' : meeting.mediaId,
269
+ mediaId: sendEmptyMediaId ? '' : meeting.mediaId,
254
270
  audioMuted: meeting.isAudioMuted(),
255
271
  videoMuted: meeting.isVideoMuted(),
256
272
  meetingId: meeting.id
@@ -347,4 +363,18 @@ export default class Roap extends StatelessWebexPlugin {
347
363
  meeting.processNextQueuedMediaUpdate();
348
364
  }
349
365
  }
366
+
367
+ /**
368
+ * Performs a TURN server discovery procedure, which involves exchanging
369
+ * some roap messages with the server. This exchange has to be done before
370
+ * any other roap messages are sent
371
+ *
372
+ * @param {Meeting} meeting
373
+ * @param {Boolean} isReconnecting should be set to true if this is a new
374
+ * media connection just after a reconnection
375
+ * @returns {Promise}
376
+ */
377
+ doTurnDiscovery(meeting, isReconnecting) {
378
+ return this.turnDiscovery.doTurnDiscovery(meeting, isReconnecting);
379
+ }
350
380
  }
@@ -116,11 +116,13 @@ export default class RoapRequest extends StatelessWebexPlugin {
116
116
  /**
117
117
  * Sends a ROAP message
118
118
  * @param {Object} options
119
- * @param {String} options.roapMessage
120
- * @param {String} options.locusId
121
- * @param {String} options.locusSelfId
119
+ * @param {Object} options.roapMessage
120
+ * @param {String} options.locusSelfUrl
122
121
  * @param {String} options.mediaId
123
122
  * @param {String} options.correlationId
123
+ * @param {Boolean} options.audioMuted
124
+ * @param {Boolean} options.videoMuted
125
+ * @param {String} options.meetingId
124
126
  * @returns {Promise} returns the response/failure of the request
125
127
  */
126
128
  sendRoap(options) {
@@ -0,0 +1,238 @@
1
+ import {Defer} from '@webex/common';
2
+
3
+ import Metrics from '../metrics';
4
+ import BEHAVIORAL_METRICS from '../metrics/constants';
5
+ import LoggerProxy from '../common/logs/logger-proxy';
6
+ import {ROAP} from '../constants';
7
+
8
+ import RoapRequest from './request';
9
+
10
+ const TURN_DISCOVERY_TIMEOUT = 10; // in seconds
11
+
12
+ /**
13
+ * Handles the process of finding out TURN server information from Linus.
14
+ * This is achieved by sending a TURN_DISCOVERY_REQUEST.
15
+ */
16
+ export default class TurnDiscovery {
17
+ private roapRequest: RoapRequest;
18
+
19
+ private defer?: Defer; // used for waiting for the response
20
+
21
+ private turnInfo: {
22
+ url: string;
23
+ username: string;
24
+ password: string;
25
+ };
26
+
27
+ private responseTimer?: ReturnType<typeof setTimeout>;
28
+
29
+ /**
30
+ * Constructor
31
+ *
32
+ * @param {RoapRequest} roapRequest
33
+ */
34
+ constructor(roapRequest: RoapRequest) {
35
+ this.roapRequest = roapRequest;
36
+ this.turnInfo = {
37
+ url: '',
38
+ username: '',
39
+ password: '',
40
+ };
41
+ }
42
+
43
+
44
+ /**
45
+ * waits for TURN_DISCOVERY_RESPONSE message to arrive
46
+ *
47
+ * @returns {Promise}
48
+ * @private
49
+ * @memberof Roap
50
+ */
51
+ waitForTurnDiscoveryResponse() {
52
+ if (!this.defer) {
53
+ LoggerProxy.logger.warn('Roap:turnDiscovery#waitForTurnDiscoveryResponse --> TURN discovery is not in progress');
54
+
55
+ return Promise.reject(new Error('waitForTurnDiscoveryResponse() called before sendRoapTurnDiscoveryRequest()'));
56
+ }
57
+
58
+ const {defer} = this;
59
+
60
+ this.responseTimer = setTimeout(() => {
61
+ LoggerProxy.logger.warn(`Roap:turnDiscovery#waitForTurnDiscoveryResponse --> timeout! no response arrived within ${TURN_DISCOVERY_TIMEOUT} seconds`);
62
+
63
+ defer.reject(new Error('Timed out waiting for TURN_DISCOVERY_RESPONSE'));
64
+ }, TURN_DISCOVERY_TIMEOUT * 1000);
65
+
66
+ LoggerProxy.logger.info('Roap:turnDiscovery#waitForTurnDiscoveryResponse --> waiting for TURN_DISCOVERY_RESPONSE...');
67
+
68
+ return defer.promise;
69
+ }
70
+
71
+ /**
72
+ * handles TURN_DISCOVERY_RESPONSE roap message
73
+ *
74
+ * @param {Object} roapMessage
75
+ * @returns {void}
76
+ * @public
77
+ * @memberof Roap
78
+ */
79
+ handleTurnDiscoveryResponse(roapMessage) {
80
+ const {headers} = roapMessage;
81
+
82
+ if (!this.defer) {
83
+ LoggerProxy.logger.warn('Roap:turnDiscovery#handleTurnDiscoveryResponse --> unexpected TURN discovery response');
84
+
85
+ return;
86
+ }
87
+
88
+ const expectedHeaders = [
89
+ {headerName: 'x-cisco-turn-url', field: 'url'},
90
+ {headerName: 'x-cisco-turn-username', field: 'username'},
91
+ {headerName: 'x-cisco-turn-password', field: 'password'},
92
+ ];
93
+
94
+ let foundHeaders = 0;
95
+
96
+ headers?.forEach((receivedHeader) => {
97
+ // check if it matches any of our expected headers
98
+ expectedHeaders.forEach((expectedHeader) => {
99
+ if (receivedHeader.startsWith(`${expectedHeader.headerName}=`)) {
100
+ this.turnInfo[expectedHeader.field] = receivedHeader.substring(expectedHeader.headerName.length + 1);
101
+ foundHeaders += 1;
102
+ }
103
+ });
104
+ });
105
+
106
+ clearTimeout(this.responseTimer);
107
+ this.responseTimer = undefined;
108
+
109
+ if (foundHeaders !== expectedHeaders.length) {
110
+ LoggerProxy.logger.warn(`Roap:turnDiscovery#handleTurnDiscoveryResponse --> missing some headers, received: ${JSON.stringify(headers)}`);
111
+ this.defer.reject(new Error(`TURN_DISCOVERY_RESPONSE missing some headers: ${JSON.stringify(headers)}`));
112
+ }
113
+ else {
114
+ LoggerProxy.logger.info(`Roap:turnDiscovery#handleTurnDiscoveryResponse --> received a valid response, url=${this.turnInfo.url}`);
115
+ this.defer.resolve();
116
+ }
117
+ }
118
+
119
+ /**
120
+ * sends the TURN_DISCOVERY_REQUEST roap request
121
+ *
122
+ * @param {Meeting} meeting
123
+ * @param {Boolean} isReconnecting
124
+ * @returns {Promise}
125
+ * @private
126
+ * @memberof Roap
127
+ */
128
+ sendRoapTurnDiscoveryRequest(meeting, isReconnecting) {
129
+ const seq = meeting.roapSeq + 1;
130
+
131
+ if (this.defer) {
132
+ LoggerProxy.logger.warn('Roap:turnDiscovery#sendRoapTurnDiscoveryRequest --> already in progress');
133
+
134
+ return Promise.resolve();
135
+ }
136
+
137
+ this.defer = new Defer();
138
+
139
+ const roapMessage = {
140
+ messageType: ROAP.ROAP_TYPES.TURN_DISCOVERY_REQUEST,
141
+ version: ROAP.ROAP_VERSION,
142
+ seq,
143
+ };
144
+
145
+ LoggerProxy.logger.info('Roap:turnDiscovery#sendRoapTurnDiscoveryRequest --> sending TURN_DISCOVERY_REQUEST');
146
+
147
+ return this.roapRequest
148
+ .sendRoap({
149
+ roapMessage,
150
+ correlationId: meeting.correlationId,
151
+ locusSelfUrl: meeting.selfUrl,
152
+ mediaId: isReconnecting ? '' : meeting.mediaId,
153
+ audioMuted: meeting.isAudioMuted(),
154
+ videoMuted: meeting.isVideoMuted(),
155
+ meetingId: meeting.id
156
+ })
157
+ .then(({mediaConnections}) => {
158
+ meeting.setRoapSeq(seq);
159
+
160
+ if (mediaConnections) {
161
+ meeting.updateMediaConnections(mediaConnections);
162
+ }
163
+ });
164
+ }
165
+
166
+ /**
167
+ * Sends the OK message that server expects to receive
168
+ * after it sends us TURN_DISCOVERY_RESPONSE
169
+ *
170
+ * @param {Meeting} meeting
171
+ * @returns {Promise}
172
+ */
173
+ sendRoapOK(meeting) {
174
+ LoggerProxy.logger.info('Roap:turnDiscovery#sendRoapOK --> sending OK');
175
+
176
+ return this.roapRequest.sendRoap({
177
+ roapMessage: {
178
+ messageType: ROAP.ROAP_TYPES.OK,
179
+ version: ROAP.ROAP_VERSION,
180
+ seq: meeting.roapSeq
181
+ },
182
+ locusSelfUrl: meeting.selfUrl,
183
+ mediaId: meeting.mediaId,
184
+ correlationId: meeting.correlationId,
185
+ audioMuted: meeting.isAudioMuted(),
186
+ videoMuted: meeting.isVideoMuted(),
187
+ meetingId: meeting.id
188
+ });
189
+ }
190
+
191
+ /**
192
+ * Retrieves TURN server information from the backend by doing
193
+ * a roap message exchange:
194
+ * client server
195
+ * | -----TURN_DISCOVERY_REQUEST-----> |
196
+ * | <----TURN_DISCOVERY_RESPONSE----- |
197
+ * | --------------OK----------------> |
198
+ *
199
+ * @param {Meeting} meeting
200
+ * @param {Boolean} isReconnecting should be set to true if this is a new
201
+ * media connection just after a reconnection
202
+ * @returns {Promise}
203
+ */
204
+ doTurnDiscovery(meeting, isReconnecting) {
205
+ if (!meeting.config.experimental.enableTurnDiscovery) {
206
+ LoggerProxy.logger.info('Roap:turnDiscovery#doTurnDiscovery --> TURN discovery disabled in config, skipping it');
207
+
208
+ return Promise.resolve(undefined);
209
+ }
210
+
211
+ return this.sendRoapTurnDiscoveryRequest(meeting, isReconnecting)
212
+ .then(() => this.waitForTurnDiscoveryResponse())
213
+ .then(() => this.sendRoapOK(meeting))
214
+ .then(() => {
215
+ this.defer = undefined;
216
+
217
+ LoggerProxy.logger.info('Roap:turnDiscovery#doTurnDiscovery --> TURN discovery completed');
218
+
219
+ return this.turnInfo;
220
+ })
221
+ .catch((e) => {
222
+ // we catch any errors and resolve with no turn information so that the normal call join flow can continue without TURN
223
+ LoggerProxy.logger.info(`Roap:turnDiscovery#doTurnDiscovery --> TURN discovery failed, continuing without TURN: ${e}`);
224
+
225
+ Metrics.sendBehavioralMetric(
226
+ BEHAVIORAL_METRICS.TURN_DISCOVERY_FAILURE,
227
+ {
228
+ correlation_id: meeting.correlationId,
229
+ locus_id: meeting.locusUrl.split('/').pop(),
230
+ reason: e.message,
231
+ stack: e.stack
232
+ }
233
+ );
234
+
235
+ return Promise.resolve(undefined);
236
+ });
237
+ }
238
+ }
package/src/roap/util.js CHANGED
@@ -80,8 +80,6 @@ RoapUtil.setRemoteDescription = (meeting, session) => {
80
80
 
81
81
  return {
82
82
  seq: session.ANSWER.seq,
83
- locusId: meeting.locusId,
84
- locusSelfId: meeting.locusInfo.self.id,
85
83
  mediaId: meeting.mediaId,
86
84
  correlationId: meeting.correlationId
87
85
  };