@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.
- package/dist/common/logs/logger-proxy.js +7 -6
- package/dist/common/logs/logger-proxy.js.map +1 -1
- package/dist/config.js +2 -1
- package/dist/config.js.map +1 -1
- package/dist/constants.js +4 -11
- package/dist/constants.js.map +1 -1
- package/dist/media/properties.js +2 -2
- package/dist/media/properties.js.map +1 -1
- package/dist/media/util.js +18 -4
- package/dist/media/util.js.map +1 -1
- package/dist/meeting/index.js +3 -1
- package/dist/meeting/index.js.map +1 -1
- package/dist/metrics/constants.js +2 -1
- package/dist/metrics/constants.js.map +1 -1
- package/dist/peer-connection-manager/index.js +6 -4
- package/dist/peer-connection-manager/index.js.map +1 -1
- package/dist/reconnection-manager/index.js +40 -14
- package/dist/reconnection-manager/index.js.map +1 -1
- package/dist/roap/handler.js +0 -2
- package/dist/roap/handler.js.map +1 -1
- package/dist/roap/index.js +36 -7
- package/dist/roap/index.js.map +1 -1
- package/dist/roap/request.js +5 -3
- package/dist/roap/request.js.map +1 -1
- package/dist/roap/turnDiscovery.js +273 -0
- package/dist/roap/turnDiscovery.js.map +1 -0
- package/dist/roap/util.js +0 -2
- package/dist/roap/util.js.map +1 -1
- package/package.json +5 -5
- package/src/common/logs/logger-proxy.js +7 -6
- package/src/config.js +2 -1
- package/src/constants.ts +3 -4
- package/src/media/properties.js +2 -2
- package/src/media/util.js +17 -7
- package/src/meeting/index.js +3 -2
- package/src/metrics/constants.js +2 -1
- package/src/peer-connection-manager/index.js +6 -3
- package/src/reconnection-manager/index.js +13 -10
- package/src/roap/handler.js +0 -2
- package/src/roap/index.js +36 -6
- package/src/roap/request.js +5 -3
- package/src/roap/turnDiscovery.ts +238 -0
- package/src/roap/util.js +0 -2
- package/test/unit/spec/meeting/index.js +35 -0
- package/test/unit/spec/peerconnection-manager/index.js +47 -4
- package/test/unit/spec/roap/index.ts +113 -0
- 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
package/dist/roap/util.js.map
CHANGED
|
@@ -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","
|
|
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.
|
|
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.
|
|
28
|
-
"@webex/internal-plugin-mercury": "2.
|
|
29
|
-
"@webex/internal-plugin-conversation": "2.
|
|
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.
|
|
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
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',
|
package/src/media/properties.js
CHANGED
|
@@ -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 = () =>
|
|
16
|
-
|
|
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) {
|
package/src/meeting/index.js
CHANGED
|
@@ -4132,8 +4132,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4132
4132
|
});
|
|
4133
4133
|
|
|
4134
4134
|
return MeetingUtil.validateOptions(options)
|
|
4135
|
-
.then(() =>
|
|
4136
|
-
|
|
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
|
|
package/src/metrics/constants.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
231
|
-
|
|
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
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
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
|
-
|
|
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
|
}
|
package/src/roap/handler.js
CHANGED
|
@@ -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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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:
|
|
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
|
}
|
package/src/roap/request.js
CHANGED
|
@@ -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 {
|
|
120
|
-
* @param {String} options.
|
|
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