@webex/plugin-meetings 3.11.0-next.4 → 3.11.0-next.6
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/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/meeting/util.js +94 -2
- package/dist/meeting/util.js.map +1 -1
- package/dist/meetings/index.js +1 -9
- package/dist/meetings/index.js.map +1 -1
- package/dist/types/meeting/util.d.ts +25 -0
- package/dist/webinar/index.js +1 -1
- package/package.json +1 -1
- package/src/meeting/util.ts +118 -1
- package/src/meetings/index.ts +1 -5
- package/test/unit/spec/meeting/utils.js +271 -22
- package/test/unit/spec/meetings/index.js +7 -44
|
@@ -2,6 +2,31 @@ import { LocalCameraStream, LocalMicrophoneStream } from '@webex/media-helpers';
|
|
|
2
2
|
import { SELF_POLICY, IP_VERSION } from '../constants';
|
|
3
3
|
declare const MeetingUtil: {
|
|
4
4
|
parseLocusJoin: (response: any) => any;
|
|
5
|
+
/**
|
|
6
|
+
* Sanitizes a WebSocket URL by extracting only protocol, host, and pathname
|
|
7
|
+
* Returns concatenated protocol + host + pathname for safe logging
|
|
8
|
+
* @param {string} urlString - The URL to sanitize
|
|
9
|
+
* @returns {string} Sanitized URL or empty string if parsing fails
|
|
10
|
+
*/
|
|
11
|
+
sanitizeWebSocketUrl: (urlString: string) => string;
|
|
12
|
+
/**
|
|
13
|
+
* Compares two URLs by protocol, host, and pathname (ignoring query params and hash)
|
|
14
|
+
* Uses sanitizeWebSocketUrl to ensure comparison matches what gets reported
|
|
15
|
+
* @param {string} url1 - First URL to compare
|
|
16
|
+
* @param {string} url2 - Second URL to compare
|
|
17
|
+
* @returns {boolean} True if URLs match, false otherwise
|
|
18
|
+
*/
|
|
19
|
+
_urlsMatch: (url1: string, url2: string) => boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Gets socket URL information for metrics, including whether the socket URLs match
|
|
22
|
+
* @param {Object} webex - The webex instance
|
|
23
|
+
* @returns {Object} Object with hasMismatchedSocket, mercurySocketUrl, and deviceSocketUrl properties
|
|
24
|
+
*/
|
|
25
|
+
getSocketUrlInfo: (webex: any) => {
|
|
26
|
+
hasMismatchedSocket: boolean;
|
|
27
|
+
mercurySocketUrl: string;
|
|
28
|
+
deviceSocketUrl: string;
|
|
29
|
+
};
|
|
5
30
|
remoteUpdateAudioVideo: (meeting: any, audioMuted?: boolean, videoMuted?: boolean) => any;
|
|
6
31
|
hasOwner: (info: any) => any;
|
|
7
32
|
isOwnerSelf: (owner: any, selfId: any) => boolean;
|
package/dist/webinar/index.js
CHANGED
package/package.json
CHANGED
package/src/meeting/util.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {LocalCameraStream, LocalMicrophoneStream} from '@webex/media-helpers';
|
|
2
|
+
import url from 'url';
|
|
2
3
|
|
|
3
4
|
import {cloneDeep} from 'lodash';
|
|
4
5
|
import {MeetingNotActiveError, UserNotJoinedError} from '../common/errors/webex-errors';
|
|
@@ -47,6 +48,111 @@ const MeetingUtil = {
|
|
|
47
48
|
return parsed;
|
|
48
49
|
},
|
|
49
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Sanitizes a WebSocket URL by extracting only protocol, host, and pathname
|
|
53
|
+
* Returns concatenated protocol + host + pathname for safe logging
|
|
54
|
+
* @param {string} urlString - The URL to sanitize
|
|
55
|
+
* @returns {string} Sanitized URL or empty string if parsing fails
|
|
56
|
+
*/
|
|
57
|
+
sanitizeWebSocketUrl: (urlString: string): string => {
|
|
58
|
+
if (!urlString || typeof urlString !== 'string') {
|
|
59
|
+
return '';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const parsedUrl = url.parse(urlString);
|
|
64
|
+
const protocol = parsedUrl.protocol || '';
|
|
65
|
+
const host = parsedUrl.host || '';
|
|
66
|
+
|
|
67
|
+
// If we don't have at least protocol and host, it's not a valid URL
|
|
68
|
+
if (!protocol || !host) {
|
|
69
|
+
return '';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const pathname = parsedUrl.pathname || '';
|
|
73
|
+
|
|
74
|
+
// Strip trailing slash if pathname is just '/'
|
|
75
|
+
const normalizedPathname = pathname === '/' ? '' : pathname;
|
|
76
|
+
|
|
77
|
+
return `${protocol}//${host}${normalizedPathname}`;
|
|
78
|
+
} catch (error) {
|
|
79
|
+
LoggerProxy.logger.warn(
|
|
80
|
+
`Meeting:util#sanitizeWebSocketUrl --> unable to parse URL: ${error}`
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
return '';
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Compares two URLs by protocol, host, and pathname (ignoring query params and hash)
|
|
89
|
+
* Uses sanitizeWebSocketUrl to ensure comparison matches what gets reported
|
|
90
|
+
* @param {string} url1 - First URL to compare
|
|
91
|
+
* @param {string} url2 - Second URL to compare
|
|
92
|
+
* @returns {boolean} True if URLs match, false otherwise
|
|
93
|
+
*/
|
|
94
|
+
_urlsMatch: (url1: string, url2: string): boolean => {
|
|
95
|
+
if (!url1 || !url2) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
const sanitized1 = MeetingUtil.sanitizeWebSocketUrl(url1);
|
|
101
|
+
const sanitized2 = MeetingUtil.sanitizeWebSocketUrl(url2);
|
|
102
|
+
|
|
103
|
+
// If either failed to parse (empty string), they don't match
|
|
104
|
+
if (!sanitized1 || !sanitized2) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return sanitized1 === sanitized2;
|
|
109
|
+
} catch (e) {
|
|
110
|
+
LoggerProxy.logger.warn('Meeting:util#_urlsMatch --> error comparing URLs', e);
|
|
111
|
+
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Gets socket URL information for metrics, including whether the socket URLs match
|
|
118
|
+
* @param {Object} webex - The webex instance
|
|
119
|
+
* @returns {Object} Object with hasMismatchedSocket, mercurySocketUrl, and deviceSocketUrl properties
|
|
120
|
+
*/
|
|
121
|
+
getSocketUrlInfo: (
|
|
122
|
+
webex: any
|
|
123
|
+
): {hasMismatchedSocket: boolean; mercurySocketUrl: string; deviceSocketUrl: string} => {
|
|
124
|
+
try {
|
|
125
|
+
const mercuryUrl = webex?.internal?.mercury?.socket?.url;
|
|
126
|
+
const deviceUrl = webex?.internal?.device?.webSocketUrl;
|
|
127
|
+
|
|
128
|
+
const sanitizedMercuryUrl = MeetingUtil.sanitizeWebSocketUrl(mercuryUrl);
|
|
129
|
+
const sanitizedDeviceUrl = MeetingUtil.sanitizeWebSocketUrl(deviceUrl);
|
|
130
|
+
|
|
131
|
+
// Only report a mismatch if both URLs are present and they don't match
|
|
132
|
+
// If either URL is missing, we can't determine if there's a mismatch, so return false
|
|
133
|
+
let hasMismatchedSocket = false;
|
|
134
|
+
if (sanitizedMercuryUrl && sanitizedDeviceUrl) {
|
|
135
|
+
hasMismatchedSocket = !MeetingUtil._urlsMatch(mercuryUrl, deviceUrl);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
hasMismatchedSocket,
|
|
140
|
+
mercurySocketUrl: sanitizedMercuryUrl,
|
|
141
|
+
deviceSocketUrl: sanitizedDeviceUrl,
|
|
142
|
+
};
|
|
143
|
+
} catch (error) {
|
|
144
|
+
LoggerProxy.logger.warn(
|
|
145
|
+
`Meeting:util#getSocketUrlInfo --> error getting socket URL info: ${error}`
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
hasMismatchedSocket: false,
|
|
150
|
+
mercurySocketUrl: '',
|
|
151
|
+
deviceSocketUrl: '',
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
|
|
50
156
|
remoteUpdateAudioVideo: (meeting, audioMuted?: boolean, videoMuted?: boolean) => {
|
|
51
157
|
if (!meeting) {
|
|
52
158
|
return Promise.reject(new ParameterError('You need a meeting object.'));
|
|
@@ -203,6 +309,7 @@ const MeetingUtil = {
|
|
|
203
309
|
const parsed = MeetingUtil.parseLocusJoin(res);
|
|
204
310
|
meeting.setLocus(parsed);
|
|
205
311
|
meeting.isoLocalClientMeetingJoinTime = res?.headers?.date; // read from header if exist, else fall back to system clock : https://jira-eng-gpk2.cisco.com/jira/browse/SPARK-555657
|
|
312
|
+
const socketUrlInfo = MeetingUtil.getSocketUrlInfo(webex);
|
|
206
313
|
webex.internal.newMetrics.submitClientEvent({
|
|
207
314
|
name: 'client.locus.join.response',
|
|
208
315
|
payload: {
|
|
@@ -210,6 +317,9 @@ const MeetingUtil = {
|
|
|
210
317
|
identifiers: {
|
|
211
318
|
trackingId: res.headers.trackingid,
|
|
212
319
|
},
|
|
320
|
+
eventData: {
|
|
321
|
+
...socketUrlInfo,
|
|
322
|
+
},
|
|
213
323
|
},
|
|
214
324
|
options: {
|
|
215
325
|
meetingId: meeting.id,
|
|
@@ -220,12 +330,19 @@ const MeetingUtil = {
|
|
|
220
330
|
return parsed;
|
|
221
331
|
})
|
|
222
332
|
.catch((err) => {
|
|
333
|
+
const socketUrlInfo = MeetingUtil.getSocketUrlInfo(webex);
|
|
223
334
|
webex.internal.newMetrics.submitClientEvent({
|
|
224
335
|
name: 'client.locus.join.response',
|
|
225
336
|
payload: {
|
|
226
337
|
identifiers: {meetingLookupUrl: meeting.meetingInfo?.meetingLookupUrl},
|
|
338
|
+
eventData: {
|
|
339
|
+
...socketUrlInfo,
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
options: {
|
|
343
|
+
meetingId: meeting.id,
|
|
344
|
+
rawError: err,
|
|
227
345
|
},
|
|
228
|
-
options: {meetingId: meeting.id, rawError: err},
|
|
229
346
|
});
|
|
230
347
|
|
|
231
348
|
throw err;
|
package/src/meetings/index.ts
CHANGED
|
@@ -1047,11 +1047,7 @@ export default class Meetings extends WebexPlugin {
|
|
|
1047
1047
|
return (
|
|
1048
1048
|
// @ts-ignore
|
|
1049
1049
|
this.webex.internal.mercury
|
|
1050
|
-
|
|
1051
|
-
// during unregister. Without this, disconnect() defaults to code 1000/"Done" which
|
|
1052
|
-
// force-closes as "Done (forced)" - a normalReconnectReason that triggers auto-reconnect,
|
|
1053
|
-
// causing a race condition with device.unregister().
|
|
1054
|
-
.disconnect({code: 3050, reason: 'meetings unregister'})
|
|
1050
|
+
.disconnect()
|
|
1055
1051
|
// @ts-ignore
|
|
1056
1052
|
.then(() => this.webex.internal.device.unregister())
|
|
1057
1053
|
.catch((error) => {
|
|
@@ -61,8 +61,9 @@ describe('plugin-meetings', () => {
|
|
|
61
61
|
meeting.trigger = sinon.stub();
|
|
62
62
|
meeting.webex = webex;
|
|
63
63
|
meeting.webex.internal.newMetrics.callDiagnosticMetrics =
|
|
64
|
-
|
|
65
|
-
meeting.webex.internal.newMetrics.callDiagnosticMetrics.clearEventLimitsForCorrelationId =
|
|
64
|
+
meeting.webex.internal.newMetrics.callDiagnosticMetrics || {};
|
|
65
|
+
meeting.webex.internal.newMetrics.callDiagnosticMetrics.clearEventLimitsForCorrelationId =
|
|
66
|
+
sinon.stub();
|
|
66
67
|
});
|
|
67
68
|
|
|
68
69
|
afterEach(() => {
|
|
@@ -245,7 +246,11 @@ describe('plugin-meetings', () => {
|
|
|
245
246
|
const response = MeetingUtil.updateLocusFromApiResponse(meeting, originalResponse);
|
|
246
247
|
|
|
247
248
|
assert.deepEqual(response, originalResponse);
|
|
248
|
-
assert.calledOnceWithExactly(
|
|
249
|
+
assert.calledOnceWithExactly(
|
|
250
|
+
meeting.locusInfo.handleLocusAPIResponse,
|
|
251
|
+
meeting,
|
|
252
|
+
originalResponse.body
|
|
253
|
+
);
|
|
249
254
|
});
|
|
250
255
|
|
|
251
256
|
it('should handle locus being missing from the response', () => {
|
|
@@ -361,8 +366,8 @@ describe('plugin-meetings', () => {
|
|
|
361
366
|
describe('remoteUpdateAudioVideo', () => {
|
|
362
367
|
it('#Should call meetingRequest.locusMediaRequest with correct parameters and return the full response', async () => {
|
|
363
368
|
const fakeResponse = {
|
|
364
|
-
body: {
|
|
365
|
-
headers: {
|
|
369
|
+
body: {locus: {url: 'locusUrl'}},
|
|
370
|
+
headers: {},
|
|
366
371
|
};
|
|
367
372
|
const meeting = {
|
|
368
373
|
id: 'meeting-id',
|
|
@@ -480,6 +485,11 @@ describe('plugin-meetings', () => {
|
|
|
480
485
|
identifiers: {
|
|
481
486
|
trackingId: 'trackingId',
|
|
482
487
|
},
|
|
488
|
+
eventData: {
|
|
489
|
+
hasMismatchedSocket: false,
|
|
490
|
+
mercurySocketUrl: '',
|
|
491
|
+
deviceSocketUrl: 'ws://example.com',
|
|
492
|
+
},
|
|
483
493
|
},
|
|
484
494
|
options: {
|
|
485
495
|
meetingId: meeting.id,
|
|
@@ -649,21 +659,26 @@ describe('plugin-meetings', () => {
|
|
|
649
659
|
it('should post client event with error when join fails', async () => {
|
|
650
660
|
const joinError = new Error('Join failed');
|
|
651
661
|
meeting.meetingRequest.joinMeeting.rejects(joinError);
|
|
652
|
-
meeting.meetingInfo = {
|
|
662
|
+
meeting.meetingInfo = {meetingLookupUrl: 'test-lookup-url'};
|
|
653
663
|
|
|
654
664
|
try {
|
|
655
665
|
await MeetingUtil.joinMeeting(meeting, {});
|
|
656
666
|
assert.fail('Expected joinMeeting to throw an error');
|
|
657
667
|
} catch (error) {
|
|
658
668
|
assert.equal(error, joinError);
|
|
659
|
-
|
|
669
|
+
|
|
660
670
|
// Verify error client event was submitted
|
|
661
671
|
assert.calledWith(webex.internal.newMetrics.submitClientEvent, {
|
|
662
672
|
name: 'client.locus.join.response',
|
|
663
673
|
payload: {
|
|
664
|
-
identifiers: {
|
|
674
|
+
identifiers: {meetingLookupUrl: 'test-lookup-url'},
|
|
675
|
+
eventData: {
|
|
676
|
+
hasMismatchedSocket: false,
|
|
677
|
+
mercurySocketUrl: '',
|
|
678
|
+
deviceSocketUrl: 'ws://example.com',
|
|
679
|
+
},
|
|
665
680
|
},
|
|
666
|
-
options: {
|
|
681
|
+
options: {meetingId: meeting.id, rawError: joinError},
|
|
667
682
|
});
|
|
668
683
|
}
|
|
669
684
|
});
|
|
@@ -721,7 +736,7 @@ describe('plugin-meetings', () => {
|
|
|
721
736
|
assert.fail('Expected joinMeetingOptions to throw PasswordError');
|
|
722
737
|
} catch (error) {
|
|
723
738
|
assert.instanceOf(error, PasswordError);
|
|
724
|
-
|
|
739
|
+
|
|
725
740
|
// Verify client event was submitted with error details
|
|
726
741
|
assert.calledWith(webex.internal.newMetrics.submitClientEvent, {
|
|
727
742
|
name: 'client.meetinginfo.response',
|
|
@@ -759,7 +774,7 @@ describe('plugin-meetings', () => {
|
|
|
759
774
|
assert.fail('Expected joinMeetingOptions to throw CaptchaError');
|
|
760
775
|
} catch (error) {
|
|
761
776
|
assert.instanceOf(error, CaptchaError);
|
|
762
|
-
|
|
777
|
+
|
|
763
778
|
// Verify client event was submitted with error details
|
|
764
779
|
assert.calledWith(webex.internal.newMetrics.submitClientEvent, {
|
|
765
780
|
name: 'client.meetinginfo.response',
|
|
@@ -972,15 +987,18 @@ describe('plugin-meetings', () => {
|
|
|
972
987
|
{functionName: 'canStartManualCaption', displayHint: 'MANUAL_CAPTION_START'},
|
|
973
988
|
{functionName: 'canStopManualCaption', displayHint: 'MANUAL_CAPTION_STOP'},
|
|
974
989
|
|
|
975
|
-
{functionName: 'isLocalRecordingStarted',displayHint:'LOCAL_RECORDING_STATUS_STARTED'},
|
|
990
|
+
{functionName: 'isLocalRecordingStarted', displayHint: 'LOCAL_RECORDING_STATUS_STARTED'},
|
|
976
991
|
{functionName: 'isLocalRecordingStopped', displayHint: 'LOCAL_RECORDING_STATUS_STOPPED'},
|
|
977
992
|
{functionName: 'isLocalRecordingPaused', displayHint: 'LOCAL_RECORDING_STATUS_PAUSED'},
|
|
978
|
-
{functionName: 'isLocalStreamingStarted',displayHint:'STREAMING_STATUS_STARTED'},
|
|
993
|
+
{functionName: 'isLocalStreamingStarted', displayHint: 'STREAMING_STATUS_STARTED'},
|
|
979
994
|
{functionName: 'isLocalStreamingStopped', displayHint: 'STREAMING_STATUS_STOPPED'},
|
|
980
995
|
|
|
981
996
|
{functionName: 'isManualCaptionActive', displayHint: 'MANUAL_CAPTION_STATUS_ACTIVE'},
|
|
982
997
|
|
|
983
|
-
{
|
|
998
|
+
{
|
|
999
|
+
functionName: 'isSpokenLanguageAutoDetectionEnabled',
|
|
1000
|
+
displayHint: 'SPOKEN_LANGUAGE_AUTO_DETECTION_ENABLED',
|
|
1001
|
+
},
|
|
984
1002
|
|
|
985
1003
|
{functionName: 'isWebexAssistantActive', displayHint: 'WEBEX_ASSISTANT_STATUS_ACTIVE'},
|
|
986
1004
|
{functionName: 'canViewCaptionPanel', displayHint: 'ENABLE_CAPTION_PANEL'},
|
|
@@ -1447,10 +1465,7 @@ describe('plugin-meetings', () => {
|
|
|
1447
1465
|
},
|
|
1448
1466
|
},
|
|
1449
1467
|
dataSets: [{name: 'dataset1', url: 'http://dataset.com'}],
|
|
1450
|
-
mediaConnections: [
|
|
1451
|
-
{mediaId: 'mediaId456'},
|
|
1452
|
-
{someOtherField: 'value'},
|
|
1453
|
-
],
|
|
1468
|
+
mediaConnections: [{mediaId: 'mediaId456'}, {someOtherField: 'value'}],
|
|
1454
1469
|
},
|
|
1455
1470
|
};
|
|
1456
1471
|
});
|
|
@@ -1500,15 +1515,249 @@ describe('plugin-meetings', () => {
|
|
|
1500
1515
|
});
|
|
1501
1516
|
|
|
1502
1517
|
it('handles mediaConnections without mediaId', () => {
|
|
1503
|
-
response.body.mediaConnections = [
|
|
1504
|
-
{someField: 'value1'},
|
|
1505
|
-
{anotherField: 'value2'},
|
|
1506
|
-
];
|
|
1518
|
+
response.body.mediaConnections = [{someField: 'value1'}, {anotherField: 'value2'}];
|
|
1507
1519
|
|
|
1508
1520
|
const result = MeetingUtil.parseLocusJoin(response);
|
|
1509
1521
|
|
|
1510
1522
|
assert.isUndefined(result.mediaId);
|
|
1511
1523
|
});
|
|
1512
1524
|
});
|
|
1525
|
+
|
|
1526
|
+
describe('#sanitizeWebSocketUrl', () => {
|
|
1527
|
+
it('extracts protocol, host, and pathname from URL', () => {
|
|
1528
|
+
const url = 'wss://example.com:443/mercury/path?token=secret&key=value#fragment';
|
|
1529
|
+
const result = MeetingUtil.sanitizeWebSocketUrl(url);
|
|
1530
|
+
|
|
1531
|
+
assert.equal(result, 'wss://example.com:443/mercury/path');
|
|
1532
|
+
});
|
|
1533
|
+
|
|
1534
|
+
it('handles URL without query string or hash', () => {
|
|
1535
|
+
const url = 'wss://example.com/path';
|
|
1536
|
+
const result = MeetingUtil.sanitizeWebSocketUrl(url);
|
|
1537
|
+
|
|
1538
|
+
assert.equal(result, 'wss://example.com/path');
|
|
1539
|
+
});
|
|
1540
|
+
|
|
1541
|
+
it('removes authentication from URL', () => {
|
|
1542
|
+
const url = 'wss://user:password@example.com/path?token=secret';
|
|
1543
|
+
const result = MeetingUtil.sanitizeWebSocketUrl(url);
|
|
1544
|
+
|
|
1545
|
+
assert.equal(result, 'wss://example.com/path');
|
|
1546
|
+
});
|
|
1547
|
+
|
|
1548
|
+
it('returns empty string for null or undefined', () => {
|
|
1549
|
+
assert.equal(MeetingUtil.sanitizeWebSocketUrl(null), '');
|
|
1550
|
+
assert.equal(MeetingUtil.sanitizeWebSocketUrl(undefined), '');
|
|
1551
|
+
});
|
|
1552
|
+
|
|
1553
|
+
it('returns empty string for non-string input', () => {
|
|
1554
|
+
assert.equal(MeetingUtil.sanitizeWebSocketUrl(123), '');
|
|
1555
|
+
assert.equal(MeetingUtil.sanitizeWebSocketUrl({}), '');
|
|
1556
|
+
});
|
|
1557
|
+
|
|
1558
|
+
it('returns empty string for invalid URL', () => {
|
|
1559
|
+
const result = MeetingUtil.sanitizeWebSocketUrl('not a valid url');
|
|
1560
|
+
|
|
1561
|
+
assert.equal(result, '');
|
|
1562
|
+
});
|
|
1563
|
+
|
|
1564
|
+
it('handles URL without pathname', () => {
|
|
1565
|
+
const url = 'wss://example.com?query=value';
|
|
1566
|
+
const result = MeetingUtil.sanitizeWebSocketUrl(url);
|
|
1567
|
+
|
|
1568
|
+
assert.equal(result, 'wss://example.com');
|
|
1569
|
+
});
|
|
1570
|
+
});
|
|
1571
|
+
|
|
1572
|
+
describe('#_urlsMatch', () => {
|
|
1573
|
+
it('returns true when URLs match (ignoring query and hash)', () => {
|
|
1574
|
+
const url1 = 'wss://example.com:443/path?token=abc#fragment1';
|
|
1575
|
+
const url2 = 'wss://example.com:443/path?token=xyz#fragment2';
|
|
1576
|
+
|
|
1577
|
+
assert.isTrue(MeetingUtil._urlsMatch(url1, url2));
|
|
1578
|
+
});
|
|
1579
|
+
|
|
1580
|
+
it('returns false when protocols differ', () => {
|
|
1581
|
+
const url1 = 'wss://example.com/path';
|
|
1582
|
+
const url2 = 'ws://example.com/path';
|
|
1583
|
+
|
|
1584
|
+
assert.isFalse(MeetingUtil._urlsMatch(url1, url2));
|
|
1585
|
+
});
|
|
1586
|
+
|
|
1587
|
+
it('returns false when hosts differ', () => {
|
|
1588
|
+
const url1 = 'wss://example1.com/path';
|
|
1589
|
+
const url2 = 'wss://example2.com/path';
|
|
1590
|
+
|
|
1591
|
+
assert.isFalse(MeetingUtil._urlsMatch(url1, url2));
|
|
1592
|
+
});
|
|
1593
|
+
|
|
1594
|
+
it('returns false when ports differ', () => {
|
|
1595
|
+
const url1 = 'wss://example.com:443/path';
|
|
1596
|
+
const url2 = 'wss://example.com:8443/path';
|
|
1597
|
+
|
|
1598
|
+
assert.isFalse(MeetingUtil._urlsMatch(url1, url2));
|
|
1599
|
+
});
|
|
1600
|
+
|
|
1601
|
+
it('returns false when pathnames differ', () => {
|
|
1602
|
+
const url1 = 'wss://example.com/path1';
|
|
1603
|
+
const url2 = 'wss://example.com/path2';
|
|
1604
|
+
|
|
1605
|
+
assert.isFalse(MeetingUtil._urlsMatch(url1, url2));
|
|
1606
|
+
});
|
|
1607
|
+
|
|
1608
|
+
it('returns false when either URL is null or undefined', () => {
|
|
1609
|
+
const url = 'wss://example.com/path';
|
|
1610
|
+
|
|
1611
|
+
assert.isFalse(MeetingUtil._urlsMatch(null, url));
|
|
1612
|
+
assert.isFalse(MeetingUtil._urlsMatch(url, null));
|
|
1613
|
+
assert.isFalse(MeetingUtil._urlsMatch(undefined, url));
|
|
1614
|
+
assert.isFalse(MeetingUtil._urlsMatch(url, undefined));
|
|
1615
|
+
});
|
|
1616
|
+
|
|
1617
|
+
it('returns false when both URLs are null', () => {
|
|
1618
|
+
assert.isFalse(MeetingUtil._urlsMatch(null, null));
|
|
1619
|
+
});
|
|
1620
|
+
|
|
1621
|
+
it('returns false when URL parsing fails', () => {
|
|
1622
|
+
const url1 = 'invalid url';
|
|
1623
|
+
const url2 = 'wss://example.com/path';
|
|
1624
|
+
|
|
1625
|
+
assert.isFalse(MeetingUtil._urlsMatch(url1, url2));
|
|
1626
|
+
});
|
|
1627
|
+
|
|
1628
|
+
it('compares URLs case-sensitively', () => {
|
|
1629
|
+
const url1 = 'wss://Example.com/path';
|
|
1630
|
+
const url2 = 'wss://example.com/path';
|
|
1631
|
+
|
|
1632
|
+
// URL parsing should handle host case-sensitivity
|
|
1633
|
+
assert.isTrue(MeetingUtil._urlsMatch(url1, url2));
|
|
1634
|
+
});
|
|
1635
|
+
});
|
|
1636
|
+
|
|
1637
|
+
describe('#getSocketUrlInfo', () => {
|
|
1638
|
+
it('returns socket URL info when URLs differ', () => {
|
|
1639
|
+
const testWebex = {
|
|
1640
|
+
internal: {
|
|
1641
|
+
mercury: {
|
|
1642
|
+
socket: {
|
|
1643
|
+
url: 'wss://mercury.example.com:443/path?token=abc',
|
|
1644
|
+
},
|
|
1645
|
+
},
|
|
1646
|
+
device: {
|
|
1647
|
+
webSocketUrl: 'wss://device.example.com:443/path?token=xyz',
|
|
1648
|
+
},
|
|
1649
|
+
},
|
|
1650
|
+
};
|
|
1651
|
+
|
|
1652
|
+
const result = MeetingUtil.getSocketUrlInfo(testWebex);
|
|
1653
|
+
|
|
1654
|
+
assert.isTrue(result.hasMismatchedSocket);
|
|
1655
|
+
assert.equal(result.mercurySocketUrl, 'wss://mercury.example.com:443/path');
|
|
1656
|
+
assert.equal(result.deviceSocketUrl, 'wss://device.example.com:443/path');
|
|
1657
|
+
});
|
|
1658
|
+
|
|
1659
|
+
it('returns socket URL info when URLs match', () => {
|
|
1660
|
+
const testWebex = {
|
|
1661
|
+
internal: {
|
|
1662
|
+
mercury: {
|
|
1663
|
+
socket: {
|
|
1664
|
+
url: 'wss://example.com:443/path?token=abc',
|
|
1665
|
+
},
|
|
1666
|
+
},
|
|
1667
|
+
device: {
|
|
1668
|
+
webSocketUrl: 'wss://example.com:443/path?token=xyz',
|
|
1669
|
+
},
|
|
1670
|
+
},
|
|
1671
|
+
};
|
|
1672
|
+
|
|
1673
|
+
const result = MeetingUtil.getSocketUrlInfo(testWebex);
|
|
1674
|
+
|
|
1675
|
+
assert.isFalse(result.hasMismatchedSocket);
|
|
1676
|
+
assert.equal(result.mercurySocketUrl, 'wss://example.com:443/path');
|
|
1677
|
+
assert.equal(result.deviceSocketUrl, 'wss://example.com:443/path');
|
|
1678
|
+
});
|
|
1679
|
+
|
|
1680
|
+
it('returns false for hasMismatchedSocket when mercury socket URL is missing', () => {
|
|
1681
|
+
const testWebex = {
|
|
1682
|
+
internal: {
|
|
1683
|
+
mercury: {
|
|
1684
|
+
socket: {},
|
|
1685
|
+
},
|
|
1686
|
+
device: {
|
|
1687
|
+
webSocketUrl: 'wss://device.example.com:443/path',
|
|
1688
|
+
},
|
|
1689
|
+
},
|
|
1690
|
+
};
|
|
1691
|
+
|
|
1692
|
+
const result = MeetingUtil.getSocketUrlInfo(testWebex);
|
|
1693
|
+
|
|
1694
|
+
assert.isFalse(result.hasMismatchedSocket);
|
|
1695
|
+
assert.equal(result.mercurySocketUrl, '');
|
|
1696
|
+
assert.equal(result.deviceSocketUrl, 'wss://device.example.com:443/path');
|
|
1697
|
+
});
|
|
1698
|
+
|
|
1699
|
+
it('returns false for hasMismatchedSocket when device socket URL is missing', () => {
|
|
1700
|
+
const testWebex = {
|
|
1701
|
+
internal: {
|
|
1702
|
+
mercury: {
|
|
1703
|
+
socket: {
|
|
1704
|
+
url: 'wss://mercury.example.com:443/path',
|
|
1705
|
+
},
|
|
1706
|
+
},
|
|
1707
|
+
device: {},
|
|
1708
|
+
},
|
|
1709
|
+
};
|
|
1710
|
+
|
|
1711
|
+
const result = MeetingUtil.getSocketUrlInfo(testWebex);
|
|
1712
|
+
|
|
1713
|
+
assert.isFalse(result.hasMismatchedSocket);
|
|
1714
|
+
assert.equal(result.mercurySocketUrl, 'wss://mercury.example.com:443/path');
|
|
1715
|
+
assert.equal(result.deviceSocketUrl, '');
|
|
1716
|
+
});
|
|
1717
|
+
|
|
1718
|
+
it('returns default values when webex object is missing properties', () => {
|
|
1719
|
+
const testWebex = {
|
|
1720
|
+
internal: {},
|
|
1721
|
+
};
|
|
1722
|
+
|
|
1723
|
+
const result = MeetingUtil.getSocketUrlInfo(testWebex);
|
|
1724
|
+
|
|
1725
|
+
assert.isFalse(result.hasMismatchedSocket);
|
|
1726
|
+
assert.equal(result.mercurySocketUrl, '');
|
|
1727
|
+
assert.equal(result.deviceSocketUrl, '');
|
|
1728
|
+
});
|
|
1729
|
+
|
|
1730
|
+
it('handles error gracefully and returns default values', () => {
|
|
1731
|
+
const testWebex = null;
|
|
1732
|
+
|
|
1733
|
+
const result = MeetingUtil.getSocketUrlInfo(testWebex);
|
|
1734
|
+
|
|
1735
|
+
assert.isFalse(result.hasMismatchedSocket);
|
|
1736
|
+
assert.equal(result.mercurySocketUrl, '');
|
|
1737
|
+
assert.equal(result.deviceSocketUrl, '');
|
|
1738
|
+
});
|
|
1739
|
+
|
|
1740
|
+
it('sanitizes URLs by removing query parameters', () => {
|
|
1741
|
+
const testWebex = {
|
|
1742
|
+
internal: {
|
|
1743
|
+
mercury: {
|
|
1744
|
+
socket: {
|
|
1745
|
+
url: 'wss://mercury.example.com/path?secret=token123&key=value',
|
|
1746
|
+
},
|
|
1747
|
+
},
|
|
1748
|
+
device: {
|
|
1749
|
+
webSocketUrl: 'wss://device.example.com/path?secret=differenttoken&key=value',
|
|
1750
|
+
},
|
|
1751
|
+
},
|
|
1752
|
+
};
|
|
1753
|
+
|
|
1754
|
+
const result = MeetingUtil.getSocketUrlInfo(testWebex);
|
|
1755
|
+
|
|
1756
|
+
assert.notInclude(result.mercurySocketUrl, 'secret');
|
|
1757
|
+
assert.notInclude(result.mercurySocketUrl, 'token123');
|
|
1758
|
+
assert.notInclude(result.deviceSocketUrl, 'secret');
|
|
1759
|
+
assert.notInclude(result.deviceSocketUrl, 'differenttoken');
|
|
1760
|
+
});
|
|
1761
|
+
});
|
|
1513
1762
|
});
|
|
1514
1763
|
});
|