@webex/plugin-meetings 3.0.0-stream-classes.3 → 3.0.0-stream-classes.5
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/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/constants.js +5 -1
- package/dist/constants.js.map +1 -1
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/index.js +25 -1
- package/dist/locus-info/index.js.map +1 -1
- package/dist/locus-info/parser.js +5 -0
- package/dist/locus-info/parser.js.map +1 -1
- package/dist/meeting/index.js +72 -15
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/locusMediaRequest.js +1 -1
- package/dist/meeting/locusMediaRequest.js.map +1 -1
- package/dist/meeting/request.js +24 -14
- package/dist/meeting/request.js.map +1 -1
- package/dist/meeting/util.js +33 -3
- package/dist/meeting/util.js.map +1 -1
- package/dist/meetings/index.js +3 -4
- package/dist/meetings/index.js.map +1 -1
- package/dist/metrics/constants.js +4 -1
- package/dist/metrics/constants.js.map +1 -1
- package/dist/multistream/remoteMediaManager.js +46 -9
- package/dist/multistream/remoteMediaManager.js.map +1 -1
- package/dist/reachability/index.js +2 -11
- package/dist/reachability/index.js.map +1 -1
- package/dist/reachability/request.js.map +1 -1
- package/dist/reconnection-manager/index.js +26 -22
- package/dist/reconnection-manager/index.js.map +1 -1
- package/dist/roap/index.js +5 -7
- package/dist/roap/index.js.map +1 -1
- package/dist/roap/request.js +1 -0
- package/dist/roap/request.js.map +1 -1
- package/dist/roap/turnDiscovery.js +3 -4
- package/dist/roap/turnDiscovery.js.map +1 -1
- package/dist/types/constants.d.ts +1 -0
- package/dist/types/meeting/index.d.ts +16 -2
- package/dist/types/meeting/locusMediaRequest.d.ts +1 -2
- package/dist/types/meeting/request.d.ts +2 -1
- package/dist/types/meeting/util.d.ts +9 -1
- package/dist/types/metrics/constants.d.ts +3 -0
- package/dist/types/multistream/remoteMediaManager.d.ts +9 -1
- package/dist/types/reachability/index.d.ts +0 -6
- package/dist/types/reachability/request.d.ts +1 -1
- package/dist/types/roap/request.d.ts +2 -1
- package/package.json +1 -1
- package/src/constants.ts +3 -2
- package/src/locus-info/index.ts +33 -2
- package/src/locus-info/parser.ts +7 -0
- package/src/meeting/index.ts +59 -16
- package/src/meeting/locusMediaRequest.ts +2 -3
- package/src/meeting/request.ts +16 -6
- package/src/meeting/util.ts +36 -2
- package/src/meetings/index.ts +2 -2
- package/src/metrics/constants.ts +3 -0
- package/src/multistream/remoteMediaManager.ts +41 -4
- package/src/reachability/index.ts +4 -10
- package/src/reachability/request.ts +1 -1
- package/src/reconnection-manager/index.ts +13 -11
- package/src/roap/index.ts +2 -4
- package/src/roap/request.ts +2 -1
- package/src/roap/turnDiscovery.ts +2 -3
- package/test/integration/spec/converged-space-meetings.js +7 -7
- package/test/integration/spec/journey.js +85 -103
- package/test/integration/spec/space-meeting.js +9 -9
- package/test/unit/spec/locus-info/index.js +118 -1
- package/test/unit/spec/meeting/index.js +95 -30
- package/test/unit/spec/meeting/locusMediaRequest.ts +1 -2
- package/test/unit/spec/meeting/request.js +23 -0
- package/test/unit/spec/meeting/utils.js +73 -4
- package/test/unit/spec/meetings/index.js +68 -3
- package/test/unit/spec/multistream/remoteMediaManager.ts +10 -2
- package/test/unit/spec/reachability/index.ts +8 -8
- package/test/unit/spec/reconnection-manager/index.js +21 -0
- package/test/unit/spec/roap/index.ts +5 -1
- package/test/unit/spec/roap/turnDiscovery.ts +11 -4
- package/test/utils/integrationTestUtils.js +4 -4
|
@@ -22,6 +22,7 @@ export default class RoapRequest extends StatelessWebexPlugin {
|
|
|
22
22
|
* @param {String} options.mediaId
|
|
23
23
|
* @param {String} options.correlationId
|
|
24
24
|
* @param {String} options.meetingId
|
|
25
|
+
* @param {IP_VERSION} options.ipVersion only required for offers
|
|
25
26
|
* @returns {Promise} returns the response/failure of the request
|
|
26
27
|
*/
|
|
27
28
|
sendRoap(options: {
|
|
@@ -29,7 +30,7 @@ export default class RoapRequest extends StatelessWebexPlugin {
|
|
|
29
30
|
locusSelfUrl: string;
|
|
30
31
|
mediaId: string;
|
|
31
32
|
meetingId: string;
|
|
32
|
-
ipVersion
|
|
33
|
+
ipVersion?: IP_VERSION;
|
|
33
34
|
locusMediaRequest?: LocusMediaRequest;
|
|
34
35
|
}): Promise<{
|
|
35
36
|
mediaConnections: any;
|
package/package.json
CHANGED
package/src/constants.ts
CHANGED
|
@@ -388,6 +388,7 @@ export const MEETING_REMOVED_REASON = {
|
|
|
388
388
|
USER_ENDED_SHARE_STREAMS: 'USER_ENDED_SHARE_STREAMS', // user triggered stop share
|
|
389
389
|
NO_MEETINGS_TO_SYNC: 'NO_MEETINGS_TO_SYNC', // After the syncMeeting no meeting exists
|
|
390
390
|
MEETING_CONNECTION_FAILED: 'MEETING_CONNECTION_FAILED', // meeting failed to connect due to ice failures or firewall issue
|
|
391
|
+
LOCUS_DTO_SYNC_FAILED: 'LOCUS_DTO_SYNC_FAILED', // failed to get any Locus DTO for that meeting
|
|
391
392
|
};
|
|
392
393
|
|
|
393
394
|
// One one one calls ends for the following reasons
|
|
@@ -1242,8 +1243,8 @@ export const DEFAULT_MEETING_INFO_REQUEST_BODY = {
|
|
|
1242
1243
|
/** the values for IP_VERSION are fixed and defined in Orpheus API */
|
|
1243
1244
|
export const IP_VERSION = {
|
|
1244
1245
|
unknown: 0,
|
|
1245
|
-
only_ipv4: 4,
|
|
1246
|
-
only_ipv6: 6,
|
|
1246
|
+
only_ipv4: 4, // we know we have ipv4, we don't know or we don't have ipv6
|
|
1247
|
+
only_ipv6: 6, // we know we have ipv6, we don't know or we don't have ipv4
|
|
1247
1248
|
ipv4_and_ipv6: 1,
|
|
1248
1249
|
} as const;
|
|
1249
1250
|
|
package/src/locus-info/index.ts
CHANGED
|
@@ -25,6 +25,8 @@ import ControlsUtils from './controlsUtils';
|
|
|
25
25
|
import EmbeddedAppsUtils from './embeddedAppsUtils';
|
|
26
26
|
import MediaSharesUtils from './mediaSharesUtils';
|
|
27
27
|
import LocusDeltaParser from './parser';
|
|
28
|
+
import Metrics from '../metrics';
|
|
29
|
+
import BEHAVIORAL_METRICS from '../metrics/constants';
|
|
28
30
|
|
|
29
31
|
/**
|
|
30
32
|
* @description LocusInfo extends ChildEmitter to convert locusInfo info a private emitter to parent object
|
|
@@ -110,6 +112,37 @@ export default class LocusInfo extends EventsScope {
|
|
|
110
112
|
// return value ignored on purpose
|
|
111
113
|
meeting.meetingRequest
|
|
112
114
|
.getLocusDTO({url})
|
|
115
|
+
.catch((e) => {
|
|
116
|
+
if (isDelta) {
|
|
117
|
+
LoggerProxy.logger.info(
|
|
118
|
+
'Locus-info:index#doLocusSync --> delta sync failed, falling back to full sync'
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.LOCUS_DELTA_SYNC_FAILED, {
|
|
122
|
+
correlationId: meeting.correlationId,
|
|
123
|
+
url,
|
|
124
|
+
reason: e.message,
|
|
125
|
+
errorName: e.name,
|
|
126
|
+
stack: e.stack,
|
|
127
|
+
code: e.code,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
isDelta = false;
|
|
131
|
+
|
|
132
|
+
return meeting.meetingRequest.getLocusDTO({url: meeting.locusUrl}).catch((err) => {
|
|
133
|
+
LoggerProxy.logger.info(
|
|
134
|
+
'Locus-info:index#doLocusSync --> fallback full sync failed, destroying the meeting'
|
|
135
|
+
);
|
|
136
|
+
this.webex.meetings.destroy(meeting, MEETING_REMOVED_REASON.LOCUS_DTO_SYNC_FAILED);
|
|
137
|
+
throw err;
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
LoggerProxy.logger.info(
|
|
141
|
+
'Locus-info:index#doLocusSync --> fallback full sync failed, destroying the meeting'
|
|
142
|
+
);
|
|
143
|
+
this.webex.meetings.destroy(meeting, MEETING_REMOVED_REASON.LOCUS_DTO_SYNC_FAILED);
|
|
144
|
+
throw e;
|
|
145
|
+
})
|
|
113
146
|
.then((res) => {
|
|
114
147
|
if (isDelta) {
|
|
115
148
|
if (!isEmpty(res.body)) {
|
|
@@ -122,8 +155,6 @@ export default class LocusInfo extends EventsScope {
|
|
|
122
155
|
} else {
|
|
123
156
|
meeting.locusInfo.onFullLocus(res.body);
|
|
124
157
|
}
|
|
125
|
-
})
|
|
126
|
-
.finally(() => {
|
|
127
158
|
// Notify parser to resume processing delta events.
|
|
128
159
|
// Any deltas in the queue that have now been superseded by this sync will simply be ignored
|
|
129
160
|
this.locusParser.resume();
|
package/src/locus-info/parser.ts
CHANGED
|
@@ -3,6 +3,9 @@ import {difference} from 'lodash';
|
|
|
3
3
|
import SortedQueue from '../common/queue';
|
|
4
4
|
import LoggerProxy from '../common/logs/logger-proxy';
|
|
5
5
|
|
|
6
|
+
import Metrics from '../metrics';
|
|
7
|
+
import BEHAVIORAL_METRICS from '../metrics/constants';
|
|
8
|
+
|
|
6
9
|
const MAX_OOO_DELTA_COUNT = 5; // when we receive an out-of-order delta and the queue builds up to MAX_OOO_DELTA_COUNT, we do a sync with Locus
|
|
7
10
|
const OOO_DELTA_WAIT_TIME = 10000; // [ms] minimum wait time before we do a sync if we get out-of-order deltas
|
|
8
11
|
const OOO_DELTA_WAIT_TIME_RANDOM_DELAY = 5000; // [ms] max random delay added to OOO_DELTA_WAIT_TIME
|
|
@@ -293,6 +296,10 @@ export default class Parser {
|
|
|
293
296
|
// the incoming locus has baseSequence from the future, so it is out-of-order,
|
|
294
297
|
// we are missing 1 or more locus that should be in front of it, we need to wait for it
|
|
295
298
|
comparison = WAIT;
|
|
299
|
+
|
|
300
|
+
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.LOCUS_DELTA_OUT_OF_ORDER, {
|
|
301
|
+
stack: new Error().stack,
|
|
302
|
+
});
|
|
296
303
|
}
|
|
297
304
|
break;
|
|
298
305
|
default:
|
package/src/meeting/index.ts
CHANGED
|
@@ -544,7 +544,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
544
544
|
meetingJoinUrl: any;
|
|
545
545
|
meetingNumber: any;
|
|
546
546
|
meetingState: any;
|
|
547
|
-
permissionToken:
|
|
547
|
+
permissionToken: string;
|
|
548
|
+
permissionTokenPayload: any;
|
|
548
549
|
resourceId: any;
|
|
549
550
|
resourceUrl: string;
|
|
550
551
|
selfId: string;
|
|
@@ -3024,7 +3025,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3024
3025
|
webexMeetingInfo?.hostId ||
|
|
3025
3026
|
this.owner;
|
|
3026
3027
|
this.permissionToken = webexMeetingInfo?.permissionToken;
|
|
3027
|
-
this.
|
|
3028
|
+
this.setPermissionTokenPayload(webexMeetingInfo?.permissionToken);
|
|
3029
|
+
this.setSelfUserPolicies();
|
|
3028
3030
|
// Need to populate environment when sending CA event
|
|
3029
3031
|
this.environment = locusMeetingObject?.info.channel || webexMeetingInfo?.channel;
|
|
3030
3032
|
}
|
|
@@ -3288,11 +3290,20 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3288
3290
|
|
|
3289
3291
|
/**
|
|
3290
3292
|
* Sets the self user policies based on the contents of the permission token
|
|
3293
|
+
* @returns {void}
|
|
3294
|
+
*/
|
|
3295
|
+
setSelfUserPolicies() {
|
|
3296
|
+
this.selfUserPolicies = this.permissionTokenPayload?.permission?.userPolicies;
|
|
3297
|
+
}
|
|
3298
|
+
|
|
3299
|
+
/**
|
|
3300
|
+
* Sets the permission token payload on the class instance
|
|
3301
|
+
*
|
|
3291
3302
|
* @param {String} permissionToken
|
|
3292
3303
|
* @returns {void}
|
|
3293
3304
|
*/
|
|
3294
|
-
|
|
3295
|
-
this.
|
|
3305
|
+
public setPermissionTokenPayload(permissionToken: string) {
|
|
3306
|
+
this.permissionTokenPayload = jwt.decode(permissionToken);
|
|
3296
3307
|
}
|
|
3297
3308
|
|
|
3298
3309
|
/**
|
|
@@ -5100,7 +5111,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5100
5111
|
errors: [
|
|
5101
5112
|
// @ts-ignore
|
|
5102
5113
|
this.webex.internal.newMetrics.callDiagnosticMetrics.getErrorPayloadForClientErrorCode(
|
|
5103
|
-
|
|
5114
|
+
{
|
|
5115
|
+
clientErrorCode: CALL_DIAGNOSTIC_CONFIG.ICE_FAILURE_CLIENT_CODE,
|
|
5116
|
+
}
|
|
5104
5117
|
),
|
|
5105
5118
|
],
|
|
5106
5119
|
},
|
|
@@ -5636,7 +5649,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5636
5649
|
errors: [
|
|
5637
5650
|
// @ts-ignore
|
|
5638
5651
|
this.webex.internal.newMetrics.callDiagnosticMetrics.getErrorPayloadForClientErrorCode(
|
|
5639
|
-
|
|
5652
|
+
{
|
|
5653
|
+
clientErrorCode: CALL_DIAGNOSTIC_CONFIG.ICE_FAILURE_CLIENT_CODE,
|
|
5654
|
+
}
|
|
5640
5655
|
),
|
|
5641
5656
|
],
|
|
5642
5657
|
},
|
|
@@ -5671,8 +5686,16 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5671
5686
|
meetingId: this.id,
|
|
5672
5687
|
},
|
|
5673
5688
|
});
|
|
5689
|
+
LoggerProxy.logger.info(
|
|
5690
|
+
`${LOG_HEADER} successfully established media connection, type=${connectionType}`
|
|
5691
|
+
);
|
|
5692
|
+
|
|
5693
|
+
// We can log ReceiveSlot SSRCs only after the SDP exchange, so doing it here:
|
|
5694
|
+
this.remoteMediaManager?.logAllReceiveSlots();
|
|
5674
5695
|
})
|
|
5675
5696
|
.catch((error) => {
|
|
5697
|
+
LoggerProxy.logger.error(`${LOG_HEADER} failed to establish media connection: `, error);
|
|
5698
|
+
|
|
5676
5699
|
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ADD_MEDIA_FAILURE, {
|
|
5677
5700
|
correlation_id: this.correlationId,
|
|
5678
5701
|
locus_id: this.locusUrl.split('/').pop(),
|
|
@@ -5712,11 +5735,6 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5712
5735
|
this.unsetPeerConnections();
|
|
5713
5736
|
}
|
|
5714
5737
|
|
|
5715
|
-
LoggerProxy.logger.error(
|
|
5716
|
-
`${LOG_HEADER} Error adding media failed to initiate PC and send request, `,
|
|
5717
|
-
error
|
|
5718
|
-
);
|
|
5719
|
-
|
|
5720
5738
|
// Upload logs on error while adding media
|
|
5721
5739
|
Trigger.trigger(
|
|
5722
5740
|
this,
|
|
@@ -5872,12 +5890,15 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5872
5890
|
return this.enqueueMediaUpdate(MEDIA_UPDATE_TYPE.UPDATE_MEDIA, options);
|
|
5873
5891
|
}
|
|
5874
5892
|
|
|
5875
|
-
if (
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
5893
|
+
if (this.isMultistream) {
|
|
5894
|
+
if (shareAudioEnabled !== undefined || shareVideoEnabled !== undefined) {
|
|
5895
|
+
throw new Error(
|
|
5896
|
+
'toggling shareAudioEnabled or shareVideoEnabled in a multistream meeting is not supported, to control receiving screen share call meeting.remoteMediaManager.setLayout() with appropriate layout'
|
|
5897
|
+
);
|
|
5898
|
+
}
|
|
5899
|
+
} else if (shareAudioEnabled !== undefined) {
|
|
5879
5900
|
throw new Error(
|
|
5880
|
-
'toggling shareAudioEnabled
|
|
5901
|
+
'toggling shareAudioEnabled in a transcoded meeting is not supported as of now'
|
|
5881
5902
|
);
|
|
5882
5903
|
}
|
|
5883
5904
|
|
|
@@ -7252,4 +7273,26 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
7252
7273
|
}
|
|
7253
7274
|
}
|
|
7254
7275
|
}
|
|
7276
|
+
|
|
7277
|
+
/**
|
|
7278
|
+
* Gets the time left in seconds till the permission token expires
|
|
7279
|
+
* (from the time the function has been fired)
|
|
7280
|
+
*
|
|
7281
|
+
* @returns {number} time left in seconds
|
|
7282
|
+
*/
|
|
7283
|
+
public getPermissionTokenTimeLeftInSec(): number | undefined {
|
|
7284
|
+
if (!this.permissionTokenPayload) {
|
|
7285
|
+
return undefined;
|
|
7286
|
+
}
|
|
7287
|
+
|
|
7288
|
+
const permissionTokenExpValue = Number(this.permissionTokenPayload.exp);
|
|
7289
|
+
|
|
7290
|
+
// using new Date instead of Date.now() to allow for accurate unit testing
|
|
7291
|
+
// https://github.com/sinonjs/fake-timers/issues/321
|
|
7292
|
+
const now = new Date().getTime();
|
|
7293
|
+
|
|
7294
|
+
// substract current time from the permissionTokenExp
|
|
7295
|
+
// (permissionTokenExp is a epoch timestamp, not a time to live duration)
|
|
7296
|
+
return (permissionTokenExpValue - now) / 1000;
|
|
7297
|
+
}
|
|
7255
7298
|
}
|
|
@@ -16,7 +16,7 @@ export type RoapRequest = {
|
|
|
16
16
|
reachability: any;
|
|
17
17
|
sequence?: any;
|
|
18
18
|
joinCookie: any; // any, because this is opaque to the client, we pass whatever object we got from one backend component (Orpheus) to the other (Locus)
|
|
19
|
-
ipVersion
|
|
19
|
+
ipVersion?: IP_VERSION;
|
|
20
20
|
};
|
|
21
21
|
|
|
22
22
|
export type LocalMuteRequest = {
|
|
@@ -28,7 +28,6 @@ export type LocalMuteRequest = {
|
|
|
28
28
|
audioMuted?: boolean;
|
|
29
29
|
videoMuted?: boolean;
|
|
30
30
|
};
|
|
31
|
-
ipVersion: IP_VERSION;
|
|
32
31
|
};
|
|
33
32
|
|
|
34
33
|
export type Request = RoapRequest | LocalMuteRequest;
|
|
@@ -204,7 +203,7 @@ export class LocusMediaRequest extends WebexPlugin {
|
|
|
204
203
|
correlationId: this.config.correlationId,
|
|
205
204
|
clientMediaPreferences: {
|
|
206
205
|
preferTranscoding: this.config.preferTranscoding,
|
|
207
|
-
ipver: request.ipVersion,
|
|
206
|
+
ipver: request.type === 'RoapMessage' ? request.ipVersion : undefined,
|
|
208
207
|
},
|
|
209
208
|
};
|
|
210
209
|
|
package/src/meeting/request.ts
CHANGED
|
@@ -108,6 +108,7 @@ export default class MeetingRequest extends StatelessWebexPlugin {
|
|
|
108
108
|
sipUri: string;
|
|
109
109
|
deviceUrl: string;
|
|
110
110
|
locusUrl: string;
|
|
111
|
+
locusClusterUrl: string;
|
|
111
112
|
resourceId: string;
|
|
112
113
|
correlationId: string;
|
|
113
114
|
ensureConversation: boolean;
|
|
@@ -124,7 +125,7 @@ export default class MeetingRequest extends StatelessWebexPlugin {
|
|
|
124
125
|
locale?: string;
|
|
125
126
|
deviceCapabilities?: Array<string>;
|
|
126
127
|
liveAnnotationSupported: boolean;
|
|
127
|
-
ipVersion
|
|
128
|
+
ipVersion?: IP_VERSION;
|
|
128
129
|
}) {
|
|
129
130
|
const {
|
|
130
131
|
asResourceOccupant,
|
|
@@ -133,6 +134,7 @@ export default class MeetingRequest extends StatelessWebexPlugin {
|
|
|
133
134
|
permissionToken,
|
|
134
135
|
deviceUrl,
|
|
135
136
|
locusUrl,
|
|
137
|
+
locusClusterUrl,
|
|
136
138
|
resourceId,
|
|
137
139
|
correlationId,
|
|
138
140
|
ensureConversation,
|
|
@@ -214,10 +216,18 @@ export default class MeetingRequest extends StatelessWebexPlugin {
|
|
|
214
216
|
url = `${locusUrl}/${PARTICIPANT}`;
|
|
215
217
|
} else if (inviteeAddress || meetingNumber) {
|
|
216
218
|
try {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
219
|
+
let clusterUrl;
|
|
220
|
+
|
|
221
|
+
if (locusClusterUrl) {
|
|
222
|
+
clusterUrl = `https://${locusClusterUrl}/locus/api/v1`;
|
|
223
|
+
} else {
|
|
224
|
+
// @ts-ignore
|
|
225
|
+
await this.webex.internal.services.waitForCatalog('postauth');
|
|
226
|
+
// @ts-ignore
|
|
227
|
+
clusterUrl = this.webex.internal.services.get('locus');
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
url = `${clusterUrl}/${LOCI}/${CALL}`;
|
|
221
231
|
body.invitee = {
|
|
222
232
|
address: inviteeAddress || `wbxmn:${meetingNumber}`,
|
|
223
233
|
};
|
|
@@ -406,7 +416,7 @@ export default class MeetingRequest extends StatelessWebexPlugin {
|
|
|
406
416
|
`Meeting:request#getLocusDTO --> Error getting latest locus, error ${err}`
|
|
407
417
|
);
|
|
408
418
|
|
|
409
|
-
|
|
419
|
+
throw err;
|
|
410
420
|
});
|
|
411
421
|
}
|
|
412
422
|
|
package/src/meeting/util.ts
CHANGED
|
@@ -13,7 +13,9 @@ import {
|
|
|
13
13
|
FULL_STATE,
|
|
14
14
|
SELF_POLICY,
|
|
15
15
|
EVENT_TRIGGERS,
|
|
16
|
+
IP_VERSION,
|
|
16
17
|
} from '../constants';
|
|
18
|
+
import BrowserDetection from '../common/browser-detection';
|
|
17
19
|
import IntentToJoinError from '../common/errors/intent-to-join';
|
|
18
20
|
import JoinMeetingError from '../common/errors/join-meeting';
|
|
19
21
|
import ParameterError from '../common/errors/parameter';
|
|
@@ -73,7 +75,6 @@ const MeetingUtil = {
|
|
|
73
75
|
audioMuted,
|
|
74
76
|
videoMuted,
|
|
75
77
|
},
|
|
76
|
-
ipVersion: meeting.getWebexObject().meetings.reachability.getIpVersion(),
|
|
77
78
|
})
|
|
78
79
|
.then((response) => {
|
|
79
80
|
// @ts-ignore
|
|
@@ -92,6 +93,38 @@ const MeetingUtil = {
|
|
|
92
93
|
|
|
93
94
|
isPinOrGuest: (err) => err?.body?.errorCode && INTENT_TO_JOIN.includes(err.body.errorCode),
|
|
94
95
|
|
|
96
|
+
/**
|
|
97
|
+
* Returns the current state of knowledge about whether we are on an ipv4-only or ipv6-only or mixed (ipv4 and ipv6) network.
|
|
98
|
+
* The return value matches the possible values of "ipver" parameter used by the backend APIs.
|
|
99
|
+
*
|
|
100
|
+
* @param {Object} webex webex instance
|
|
101
|
+
* @returns {IP_VERSION|undefined} ipver value to be passed to the backend APIs or undefined if we should not pass any value to the backend
|
|
102
|
+
*/
|
|
103
|
+
getIpVersion(webex: any): IP_VERSION | undefined {
|
|
104
|
+
const {supportsIpV4, supportsIpV6} = webex.internal.device.ipNetworkDetector;
|
|
105
|
+
|
|
106
|
+
if (BrowserDetection().isBrowser('firefox')) {
|
|
107
|
+
// our ipv6 solution relies on FQDN ICE candidates, but Firefox doesn't support them,
|
|
108
|
+
// see https://bugzilla.mozilla.org/show_bug.cgi?id=1713128
|
|
109
|
+
// so for Firefox we don't want the backend to activate the "ipv6 feature"
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (supportsIpV4 && supportsIpV6) {
|
|
114
|
+
return IP_VERSION.ipv4_and_ipv6;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (supportsIpV4) {
|
|
118
|
+
return IP_VERSION.only_ipv4;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (supportsIpV6) {
|
|
122
|
+
return IP_VERSION.only_ipv6;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return IP_VERSION.unknown;
|
|
126
|
+
},
|
|
127
|
+
|
|
95
128
|
joinMeeting: (meeting, options) => {
|
|
96
129
|
if (!meeting) {
|
|
97
130
|
return Promise.reject(new ParameterError('You need a meeting object.'));
|
|
@@ -113,6 +146,7 @@ const MeetingUtil = {
|
|
|
113
146
|
meetingNumber: meeting.meetingNumber,
|
|
114
147
|
deviceUrl: meeting.deviceUrl,
|
|
115
148
|
locusUrl: meeting.locusUrl,
|
|
149
|
+
locusClusterUrl: meeting.meetingInfo?.locusClusterUrl,
|
|
116
150
|
correlationId: meeting.correlationId,
|
|
117
151
|
roapMessage: options.roapMessage,
|
|
118
152
|
permissionToken: meeting.permissionToken,
|
|
@@ -126,7 +160,7 @@ const MeetingUtil = {
|
|
|
126
160
|
locale: options.locale,
|
|
127
161
|
deviceCapabilities: options.deviceCapabilities,
|
|
128
162
|
liveAnnotationSupported: options.liveAnnotationSupported,
|
|
129
|
-
ipVersion: meeting.getWebexObject()
|
|
163
|
+
ipVersion: MeetingUtil.getIpVersion(meeting.getWebexObject()),
|
|
130
164
|
})
|
|
131
165
|
.then((res) => {
|
|
132
166
|
// @ts-ignore
|
package/src/meetings/index.ts
CHANGED
|
@@ -887,6 +887,7 @@ export default class Meetings extends WebexPlugin {
|
|
|
887
887
|
'Meetings:index#uploadLogs --> Upload logs for meeting completed.',
|
|
888
888
|
uploadResult
|
|
889
889
|
);
|
|
890
|
+
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.UPLOAD_LOGS_SUCCESS, options);
|
|
890
891
|
Trigger.trigger(
|
|
891
892
|
this,
|
|
892
893
|
{
|
|
@@ -921,8 +922,7 @@ export default class Meetings extends WebexPlugin {
|
|
|
921
922
|
);
|
|
922
923
|
|
|
923
924
|
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.UPLOAD_LOGS_FAILURE, {
|
|
924
|
-
|
|
925
|
-
meetingId: options.meetingsId,
|
|
925
|
+
...options,
|
|
926
926
|
reason: uploadError.message,
|
|
927
927
|
stack: uploadError.stack,
|
|
928
928
|
code: uploadError.code,
|
package/src/metrics/constants.ts
CHANGED
|
@@ -40,6 +40,7 @@ const BEHAVIORAL_METRICS = {
|
|
|
40
40
|
PEERCONNECTION_FAILURE: 'js_sdk_peerConnection_failures',
|
|
41
41
|
INVALID_ICE_CANDIDATE: 'js_sdk_invalid_ice_candidate',
|
|
42
42
|
UPLOAD_LOGS_FAILURE: 'js_sdk_upload_logs_failure',
|
|
43
|
+
UPLOAD_LOGS_SUCCESS: 'js_sdk_upload_logs_success',
|
|
43
44
|
RECEIVE_TRANSCRIPTION_FAILURE: 'js_sdk_receive_transcription_failure',
|
|
44
45
|
FETCH_MEETING_INFO_V1_SUCCESS: 'js_sdk_fetch_meeting_info_v1_success',
|
|
45
46
|
FETCH_MEETING_INFO_V1_FAILURE: 'js_sdk_fetch_meeting_info_v1_failure',
|
|
@@ -54,6 +55,8 @@ const BEHAVIORAL_METRICS = {
|
|
|
54
55
|
MOVE_FROM_FAILURE: 'js_sdk_move_from_failure',
|
|
55
56
|
TURN_DISCOVERY_FAILURE: 'js_sdk_turn_discovery_failure',
|
|
56
57
|
MEETING_INFO_POLICY_ERROR: 'js_sdk_meeting_info_policy_error',
|
|
58
|
+
LOCUS_DELTA_SYNC_FAILED: 'js_sdk_locus_delta_sync_failed',
|
|
59
|
+
LOCUS_DELTA_OUT_OF_ORDER: 'js_sdk_locus_delta_ooo',
|
|
57
60
|
};
|
|
58
61
|
|
|
59
62
|
export {BEHAVIORAL_METRICS as default};
|
|
@@ -473,6 +473,8 @@ export class RemoteMediaManager extends EventsScope {
|
|
|
473
473
|
if (!this.started) {
|
|
474
474
|
throw new Error('setLayout() called before start()');
|
|
475
475
|
}
|
|
476
|
+
LoggerProxy.logger.log(`RemoteMediaManager#setLayout --> new layout selected: ${layoutId}`);
|
|
477
|
+
|
|
476
478
|
this.currentLayoutId = layoutId;
|
|
477
479
|
this.currentLayout = cloneDeep(this.config.video.layouts[this.currentLayoutId]);
|
|
478
480
|
|
|
@@ -725,10 +727,12 @@ export class RemoteMediaManager extends EventsScope {
|
|
|
725
727
|
/**
|
|
726
728
|
* Logs the state of the receive slots
|
|
727
729
|
*/
|
|
728
|
-
private
|
|
730
|
+
private logMainVideoReceiveSlots() {
|
|
729
731
|
let logMessage = '';
|
|
730
732
|
forEach(this.receiveSlotAllocations.activeSpeaker, (group, groupName) => {
|
|
731
|
-
logMessage +=
|
|
733
|
+
logMessage += `\ngroup: ${groupName}\n${group.slots
|
|
734
|
+
.map((slot) => slot.logString)
|
|
735
|
+
.join(', ')}`;
|
|
732
736
|
});
|
|
733
737
|
|
|
734
738
|
logMessage += '\nreceiverSelected:\n';
|
|
@@ -737,10 +741,43 @@ export class RemoteMediaManager extends EventsScope {
|
|
|
737
741
|
});
|
|
738
742
|
|
|
739
743
|
LoggerProxy.logger.log(
|
|
740
|
-
`RemoteMediaManager#
|
|
744
|
+
`RemoteMediaManager#logMainVideoReceiveSlots --> MAIN VIDEO receive slots: unused=${this.slots.video.unused.length}, activeSpeaker=${this.slots.video.activeSpeaker.length}, receiverSelected=${this.slots.video.receiverSelected.length}${logMessage}`
|
|
745
|
+
);
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
/** logs main audio slots */
|
|
749
|
+
private logMainAudioReceiveSlots() {
|
|
750
|
+
LoggerProxy.logger.log(
|
|
751
|
+
`RemoteMediaManager#logMainAudioReceiveSlots --> MAIN AUDIO receive slots: ${this.slots.audio
|
|
752
|
+
.map((slot) => slot.logString)
|
|
753
|
+
.join(', ')}`
|
|
741
754
|
);
|
|
742
755
|
}
|
|
743
756
|
|
|
757
|
+
/** logs slides video slots */
|
|
758
|
+
private logSlidesVideoReceiveSlots() {
|
|
759
|
+
LoggerProxy.logger.log(
|
|
760
|
+
`RemoteMediaManager#logSlidesVideoReceiveSlots --> SLIDES VIDEO receive slot: ${this.slots.screenShare.video?.logString}`
|
|
761
|
+
);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
/** logs slides audio slots */
|
|
765
|
+
private logSlidesAudioReceiveSlots() {
|
|
766
|
+
LoggerProxy.logger.log(
|
|
767
|
+
`RemoteMediaManager#logSlidesAudioReceiveSlots --> SLIDES AUDIO receive slots: ${this.slots.screenShare.audio
|
|
768
|
+
.map((slot) => slot.logString)
|
|
769
|
+
.join(', ')}`
|
|
770
|
+
);
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
/** Logs all current receive slots */
|
|
774
|
+
public logAllReceiveSlots() {
|
|
775
|
+
this.logMainVideoReceiveSlots();
|
|
776
|
+
this.logMainAudioReceiveSlots();
|
|
777
|
+
this.logSlidesVideoReceiveSlots();
|
|
778
|
+
this.logSlidesAudioReceiveSlots();
|
|
779
|
+
}
|
|
780
|
+
|
|
744
781
|
/**
|
|
745
782
|
* Makes sure we have the right number of receive slots created for the current layout
|
|
746
783
|
* and allocates them to the right video panes / pane groups
|
|
@@ -765,7 +802,7 @@ export class RemoteMediaManager extends EventsScope {
|
|
|
765
802
|
// allocate receiver selected
|
|
766
803
|
this.allocateSlotsToReceiverSelectedVideoPaneGroups();
|
|
767
804
|
|
|
768
|
-
this.
|
|
805
|
+
this.logMainVideoReceiveSlots();
|
|
769
806
|
|
|
770
807
|
// If this is the initial layout, there may be some "unused" slots left because of the preallocation
|
|
771
808
|
// done in this.preallocateVideoReceiveSlots(), so release them now
|
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
import _ from 'lodash';
|
|
8
8
|
|
|
9
9
|
import LoggerProxy from '../common/logs/logger-proxy';
|
|
10
|
-
import
|
|
10
|
+
import MeetingUtil from '../meeting/util';
|
|
11
|
+
|
|
12
|
+
import {ICE_GATHERING_STATE, CONNECTION_STATE, REACHABILITY} from '../constants';
|
|
11
13
|
|
|
12
14
|
import ReachabilityRequest from './request';
|
|
13
15
|
|
|
@@ -71,7 +73,7 @@ export default class Reachability {
|
|
|
71
73
|
// Fetch clusters and measure latency
|
|
72
74
|
try {
|
|
73
75
|
const {clusters, joinCookie} = await this.reachabilityRequest.getClusters(
|
|
74
|
-
|
|
76
|
+
MeetingUtil.getIpVersion(this.webex)
|
|
75
77
|
);
|
|
76
78
|
|
|
77
79
|
// Perform Reachability Check
|
|
@@ -134,14 +136,6 @@ export default class Reachability {
|
|
|
134
136
|
return reachable;
|
|
135
137
|
}
|
|
136
138
|
|
|
137
|
-
/**
|
|
138
|
-
* Returns what we know about the IP version of the networks we're connected to.
|
|
139
|
-
* @returns {IP_VERSION}
|
|
140
|
-
*/
|
|
141
|
-
getIpVersion(): IP_VERSION {
|
|
142
|
-
return IP_VERSION.unknown;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
139
|
/**
|
|
146
140
|
* Generate peerConnection config settings
|
|
147
141
|
* @param {object} cluster
|
|
@@ -33,7 +33,7 @@ class ReachabilityRequest {
|
|
|
33
33
|
* @param {IP_VERSION} ipVersion information about current ip network we're on
|
|
34
34
|
* @returns {Promise}
|
|
35
35
|
*/
|
|
36
|
-
getClusters = (ipVersion
|
|
36
|
+
getClusters = (ipVersion?: IP_VERSION): Promise<{clusters: ClusterList; joinCookie: any}> =>
|
|
37
37
|
this.webex
|
|
38
38
|
.request({
|
|
39
39
|
method: HTTP_VERBS.GET,
|
|
@@ -443,17 +443,19 @@ export default class ReconnectionManager {
|
|
|
443
443
|
}
|
|
444
444
|
}
|
|
445
445
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
446
|
+
if (!this.webex.credentials.isUnverifiedGuest) {
|
|
447
|
+
try {
|
|
448
|
+
LoggerProxy.logger.info(
|
|
449
|
+
'ReconnectionManager:index#executeReconnection --> Updating meeting data from server.'
|
|
450
|
+
);
|
|
451
|
+
await this.webex.meetings.syncMeetings();
|
|
452
|
+
} catch (syncError) {
|
|
453
|
+
LoggerProxy.logger.info(
|
|
454
|
+
'ReconnectionManager:index#executeReconnection --> Unable to sync meetings, reconnecting.',
|
|
455
|
+
syncError
|
|
456
|
+
);
|
|
457
|
+
throw new NeedsRetryError(syncError);
|
|
458
|
+
}
|
|
457
459
|
}
|
|
458
460
|
|
|
459
461
|
// TODO: try to improve this logic as the reconnection manager saves the instance of deleted meeting object
|
package/src/roap/index.ts
CHANGED
|
@@ -7,6 +7,7 @@ import LoggerProxy from '../common/logs/logger-proxy';
|
|
|
7
7
|
import RoapRequest from './request';
|
|
8
8
|
import TurnDiscovery from './turnDiscovery';
|
|
9
9
|
import Meeting from '../meeting';
|
|
10
|
+
import MeetingUtil from '../meeting/util';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Roap options
|
|
@@ -100,7 +101,6 @@ export default class Roap extends StatelessWebexPlugin {
|
|
|
100
101
|
mediaId: options.mediaId,
|
|
101
102
|
meetingId: meeting.id,
|
|
102
103
|
locusMediaRequest: meeting.locusMediaRequest,
|
|
103
|
-
ipVersion: meeting.webex.meetings.reachability.getIpVersion(),
|
|
104
104
|
})
|
|
105
105
|
.then(() => {
|
|
106
106
|
LoggerProxy.logger.log(`Roap:index#sendRoapOK --> ROAP OK sent with seq ${options.seq}`);
|
|
@@ -135,7 +135,6 @@ export default class Roap extends StatelessWebexPlugin {
|
|
|
135
135
|
mediaId: options.mediaId,
|
|
136
136
|
meetingId: meeting.id,
|
|
137
137
|
locusMediaRequest: meeting.locusMediaRequest,
|
|
138
|
-
ipVersion: meeting.webex.meetings.reachability.getIpVersion(),
|
|
139
138
|
});
|
|
140
139
|
}
|
|
141
140
|
|
|
@@ -165,7 +164,6 @@ export default class Roap extends StatelessWebexPlugin {
|
|
|
165
164
|
mediaId: options.mediaId,
|
|
166
165
|
meetingId: meeting.id,
|
|
167
166
|
locusMediaRequest: meeting.locusMediaRequest,
|
|
168
|
-
ipVersion: meeting.webex.meetings.reachability.getIpVersion(),
|
|
169
167
|
})
|
|
170
168
|
.then(() => {
|
|
171
169
|
LoggerProxy.logger.log(
|
|
@@ -204,7 +202,7 @@ export default class Roap extends StatelessWebexPlugin {
|
|
|
204
202
|
meetingId: meeting.id,
|
|
205
203
|
preferTranscoding: !meeting.isMultistream,
|
|
206
204
|
locusMediaRequest: meeting.locusMediaRequest,
|
|
207
|
-
ipVersion: meeting.webex
|
|
205
|
+
ipVersion: MeetingUtil.getIpVersion(meeting.webex),
|
|
208
206
|
})
|
|
209
207
|
.then(({locus, mediaConnections}) => {
|
|
210
208
|
if (mediaConnections) {
|
package/src/roap/request.ts
CHANGED
|
@@ -63,6 +63,7 @@ export default class RoapRequest extends StatelessWebexPlugin {
|
|
|
63
63
|
* @param {String} options.mediaId
|
|
64
64
|
* @param {String} options.correlationId
|
|
65
65
|
* @param {String} options.meetingId
|
|
66
|
+
* @param {IP_VERSION} options.ipVersion only required for offers
|
|
66
67
|
* @returns {Promise} returns the response/failure of the request
|
|
67
68
|
*/
|
|
68
69
|
async sendRoap(options: {
|
|
@@ -70,7 +71,7 @@ export default class RoapRequest extends StatelessWebexPlugin {
|
|
|
70
71
|
locusSelfUrl: string;
|
|
71
72
|
mediaId: string;
|
|
72
73
|
meetingId: string;
|
|
73
|
-
ipVersion
|
|
74
|
+
ipVersion?: IP_VERSION;
|
|
74
75
|
locusMediaRequest?: LocusMediaRequest;
|
|
75
76
|
}) {
|
|
76
77
|
const {roapMessage, locusSelfUrl, mediaId, meetingId, locusMediaRequest, ipVersion} = options;
|