@webex/plugin-meetings 3.10.0-next.26 → 3.10.0-next.28

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/src/config.ts CHANGED
@@ -100,5 +100,6 @@ export default {
100
100
  logUploadIntervalMultiplicationFactor: 0, // if set to 0 or undefined, logs won't be uploaded periodically, if you want periodic logs, recommended value is 1
101
101
  stopIceGatheringAfterFirstRelayCandidate: false,
102
102
  enableAudioTwccForMultistream: false,
103
+ enablePerUdpUrlReachability: false, // true: separate peer connection per each UDP URL; false: single peer connection for all URLs
103
104
  },
104
105
  };
@@ -1113,11 +1113,7 @@ class HashTreeParser {
1113
1113
 
1114
1114
  const url = `${dataSet.url}/sync`;
1115
1115
  const body = {
1116
- dataSet: {
1117
- name: dataSet.name,
1118
- leafCount: dataSet.leafCount,
1119
- root: dataSet.hashTree?.getRootHash(),
1120
- },
1116
+ leafCount: dataSet.leafCount,
1121
1117
  leafDataEntries: [],
1122
1118
  };
1123
1119
 
@@ -980,8 +980,8 @@ export default class LocusInfo extends EventsScope {
980
980
  // eslint-disable-next-line @typescript-eslint/no-shadow
981
981
  handleOneOnOneEvent(eventType: string) {
982
982
  if (
983
- this.parsedLocus.fullState.type === _CALL_ ||
984
- this.parsedLocus.fullState.type === _SIP_BRIDGE_
983
+ this.parsedLocus.fullState?.type === _CALL_ ||
984
+ this.parsedLocus.fullState?.type === _SIP_BRIDGE_
985
985
  ) {
986
986
  // for 1:1 bob calls alice and alice declines, notify the meeting state
987
987
  if (eventType === LOCUSEVENT.PARTICIPANT_DECLINED) {
@@ -1094,9 +1094,9 @@ export default class LocusInfo extends EventsScope {
1094
1094
  */
1095
1095
  isMeetingActive() {
1096
1096
  if (
1097
- this.parsedLocus.fullState.type === _CALL_ ||
1098
- this.parsedLocus.fullState.type === _SIP_BRIDGE_ ||
1099
- this.parsedLocus.fullState.type === _SPACE_SHARE_
1097
+ this.parsedLocus.fullState?.type === _CALL_ ||
1098
+ this.parsedLocus.fullState?.type === _SIP_BRIDGE_ ||
1099
+ this.parsedLocus.fullState?.type === _SPACE_SHARE_
1100
1100
  ) {
1101
1101
  // @ts-ignore
1102
1102
  const partner = this.getLocusPartner(this.participants, this.self);
@@ -1189,7 +1189,7 @@ export default class LocusInfo extends EventsScope {
1189
1189
  }
1190
1190
  );
1191
1191
  }
1192
- } else if (this.parsedLocus.fullState.type === _MEETING_) {
1192
+ } else if (this.parsedLocus.fullState?.type === _MEETING_) {
1193
1193
  if (
1194
1194
  this.fullState &&
1195
1195
  (this.fullState.state === LOCUS.STATE.INACTIVE ||
@@ -1286,6 +1286,7 @@ export default class LocusInfo extends EventsScope {
1286
1286
  compareSelfAndHost() {
1287
1287
  // In some cases the host info is not present but the moderator values changes from null to false so it triggers an update
1288
1288
  if (
1289
+ this.parsedLocus.self &&
1289
1290
  this.parsedLocus.self.selfIdentity === this.parsedLocus.host?.hostId &&
1290
1291
  this.parsedLocus.self.moderator
1291
1292
  ) {
@@ -1970,14 +1971,14 @@ export default class LocusInfo extends EventsScope {
1970
1971
  }
1971
1972
 
1972
1973
  // TODO: check if we need to save the sipUri here as well
1973
- // this.emit(LOCUSINFO.EVENTS.MEETING_UPDATE, SelfUtils.getSipUrl(this.getLocusPartner(participants, self), this.parsedLocus.fullState.type, this.parsedLocus.info.sipUri));
1974
+ // this.emit(LOCUSINFO.EVENTS.MEETING_UPDATE, SelfUtils.getSipUrl(this.getLocusPartner(participants, self), this.parsedLocus.fullState?.type, this.parsedLocus.info?.sipUri));
1974
1975
  const result = SelfUtils.getSipUrl(
1975
1976
  this.getLocusPartner(this.participants, self),
1976
- this.parsedLocus.fullState.type,
1977
- this.parsedLocus.info.sipUri
1977
+ this.parsedLocus.fullState?.type,
1978
+ this.parsedLocus.info?.sipUri
1978
1979
  );
1979
1980
 
1980
- if (result.sipUri) {
1981
+ if (result?.sipUri) {
1981
1982
  this.updateMeeting(result);
1982
1983
  }
1983
1984
 
@@ -1,5 +1,6 @@
1
1
  import {ClusterNode} from './request';
2
2
  import EventsScope from '../common/events/events-scope';
3
+ import LoggerProxy from '../common/logs/logger-proxy';
3
4
 
4
5
  import {Enum} from '../constants';
5
6
  import {
@@ -37,36 +38,117 @@ export type Events = Enum<typeof Events>;
37
38
 
38
39
  /**
39
40
  * A class that handles reachability checks for a single cluster.
40
- * Creates and orchestrates a ReachabilityPeerConnection instance.
41
+ * Creates and orchestrates ReachabilityPeerConnection instance(s).
41
42
  * Listens to events and emits them to consumers.
43
+ *
44
+ * When enablePerUdpUrlReachability is true:
45
+ * - Creates one ReachabilityPeerConnection for each UDP URL
46
+ * - Creates one ReachabilityPeerConnection for all TCP and TLS URLs together
47
+ * Otherwise:
48
+ * - Creates a single ReachabilityPeerConnection for all URLs
42
49
  */
43
50
  export class ClusterReachability extends EventsScope {
44
- private reachabilityPeerConnection: ReachabilityPeerConnection;
51
+ private reachabilityPeerConnection: ReachabilityPeerConnection | null = null;
52
+ private reachabilityPeerConnectionsForUdp: ReachabilityPeerConnection[] = [];
53
+
45
54
  public readonly isVideoMesh: boolean;
46
55
  public readonly name;
47
56
  public readonly reachedSubnets: Set<string> = new Set();
48
57
 
58
+ private enablePerUdpUrlReachability: boolean;
59
+ private udpResultEmitted = false;
60
+
49
61
  /**
50
62
  * Constructor for ClusterReachability
51
63
  * @param {string} name cluster name
52
64
  * @param {ClusterNode} clusterInfo information about the media cluster
65
+ * @param {boolean} enablePerUdpUrlReachability whether to create separate peer connections per UDP URL
53
66
  */
54
- constructor(name: string, clusterInfo: ClusterNode) {
67
+ constructor(name: string, clusterInfo: ClusterNode, enablePerUdpUrlReachability = false) {
55
68
  super();
56
69
  this.name = name;
57
70
  this.isVideoMesh = clusterInfo.isVideoMesh;
71
+ this.enablePerUdpUrlReachability = enablePerUdpUrlReachability;
58
72
 
59
- this.reachabilityPeerConnection = new ReachabilityPeerConnection(name, clusterInfo);
73
+ if (this.enablePerUdpUrlReachability) {
74
+ this.initializePerUdpUrlReachabilityCheck(clusterInfo);
75
+ } else {
76
+ this.initializeSingleReachabilityPeerConnection(clusterInfo);
77
+ }
78
+ }
60
79
 
61
- this.setupReachabilityPeerConnectionEventListeners();
80
+ /**
81
+ * Initializes a single ReachabilityPeerConnection for all protocols
82
+ * @param {ClusterNode} clusterInfo information about the media cluster
83
+ * @returns {void}
84
+ */
85
+ private initializeSingleReachabilityPeerConnection(clusterInfo: ClusterNode) {
86
+ this.reachabilityPeerConnection = new ReachabilityPeerConnection(this.name, clusterInfo);
87
+ this.setupReachabilityPeerConnectionEventListeners(this.reachabilityPeerConnection);
62
88
  }
63
89
 
64
90
  /**
65
- * Sets up event listeners for the ReachabilityPeerConnection instance
91
+ * Initializes per-URL UDP reachability checks:
92
+ * - One ReachabilityPeerConnection per UDP URL
93
+ * - One ReachabilityPeerConnection for all TCP and TLS URLs together
94
+ * @param {ClusterNode} clusterInfo information about the media cluster
66
95
  * @returns {void}
67
96
  */
68
- private setupReachabilityPeerConnectionEventListeners() {
69
- this.reachabilityPeerConnection.on(ReachabilityPeerConnectionEvents.resultReady, (data) => {
97
+ private initializePerUdpUrlReachabilityCheck(clusterInfo: ClusterNode) {
98
+ LoggerProxy.logger.log(
99
+ `ClusterReachability#initializePerUdpUrlReachabilityCheck --> cluster: ${this.name}, performing per-URL UDP reachability for ${clusterInfo.udp.length} URLs`
100
+ );
101
+
102
+ // Create one ReachabilityPeerConnection for each UDP URL
103
+ clusterInfo.udp.forEach((udpUrl) => {
104
+ const singleUdpClusterInfo: ClusterNode = {
105
+ isVideoMesh: clusterInfo.isVideoMesh,
106
+ udp: [udpUrl],
107
+ tcp: [],
108
+ xtls: [],
109
+ };
110
+ const rpc = new ReachabilityPeerConnection(this.name, singleUdpClusterInfo);
111
+ this.setupReachabilityPeerConnectionEventListeners(rpc, true);
112
+ this.reachabilityPeerConnectionsForUdp.push(rpc);
113
+ });
114
+
115
+ // Create one ReachabilityPeerConnection for all TCP and TLS URLs together
116
+ if (clusterInfo.tcp.length > 0 || clusterInfo.xtls.length > 0) {
117
+ const tcpTlsClusterInfo: ClusterNode = {
118
+ isVideoMesh: clusterInfo.isVideoMesh,
119
+ udp: [],
120
+ tcp: clusterInfo.tcp,
121
+ xtls: clusterInfo.xtls,
122
+ };
123
+ this.reachabilityPeerConnection = new ReachabilityPeerConnection(
124
+ this.name,
125
+ tcpTlsClusterInfo
126
+ );
127
+ this.setupReachabilityPeerConnectionEventListeners(this.reachabilityPeerConnection);
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Sets up event listeners for a ReachabilityPeerConnection instance
133
+ * @param {ReachabilityPeerConnection} rpc the ReachabilityPeerConnection instance
134
+ * @param {boolean} isUdpPerUrl whether this is a per-URL UDP instance
135
+ * @returns {void}
136
+ */
137
+ private setupReachabilityPeerConnectionEventListeners(
138
+ rpc: ReachabilityPeerConnection,
139
+ isUdpPerUrl = false
140
+ ) {
141
+ rpc.on(ReachabilityPeerConnectionEvents.resultReady, (data) => {
142
+ // For per-URL UDP checks, only emit the first successful UDP result
143
+ if (isUdpPerUrl && data.protocol === 'udp') {
144
+ if (this.udpResultEmitted) {
145
+ return;
146
+ }
147
+ if (data.result === 'reachable') {
148
+ this.udpResultEmitted = true;
149
+ }
150
+ }
151
+
70
152
  this.emit(
71
153
  {
72
154
  file: 'clusterReachability',
@@ -77,21 +159,18 @@ export class ClusterReachability extends EventsScope {
77
159
  );
78
160
  });
79
161
 
80
- this.reachabilityPeerConnection.on(
81
- ReachabilityPeerConnectionEvents.clientMediaIpsUpdated,
82
- (data) => {
83
- this.emit(
84
- {
85
- file: 'clusterReachability',
86
- function: 'setupReachabilityPeerConnectionEventListeners',
87
- },
88
- Events.clientMediaIpsUpdated,
89
- data
90
- );
91
- }
92
- );
162
+ rpc.on(ReachabilityPeerConnectionEvents.clientMediaIpsUpdated, (data) => {
163
+ this.emit(
164
+ {
165
+ file: 'clusterReachability',
166
+ function: 'setupReachabilityPeerConnectionEventListeners',
167
+ },
168
+ Events.clientMediaIpsUpdated,
169
+ data
170
+ );
171
+ });
93
172
 
94
- this.reachabilityPeerConnection.on(ReachabilityPeerConnectionEvents.natTypeUpdated, (data) => {
173
+ rpc.on(ReachabilityPeerConnectionEvents.natTypeUpdated, (data) => {
95
174
  this.emit(
96
175
  {
97
176
  file: 'clusterReachability',
@@ -102,18 +181,54 @@ export class ClusterReachability extends EventsScope {
102
181
  );
103
182
  });
104
183
 
105
- this.reachabilityPeerConnection.on(ReachabilityPeerConnectionEvents.reachedSubnets, (data) => {
106
- data.subnets.forEach((subnet) => {
184
+ rpc.on(ReachabilityPeerConnectionEvents.reachedSubnets, (data) => {
185
+ data.subnets.forEach((subnet: string) => {
107
186
  this.reachedSubnets.add(subnet);
108
187
  });
109
188
  });
110
189
  }
111
190
 
112
191
  /**
192
+ * Gets the aggregated reachability result for this cluster.
113
193
  * @returns {ClusterReachabilityResult} reachability result for this cluster
114
194
  */
115
195
  getResult(): ClusterReachabilityResult {
116
- return this.reachabilityPeerConnection.getResult();
196
+ if (!this.enablePerUdpUrlReachability) {
197
+ return (
198
+ this.reachabilityPeerConnection?.getResult() ?? {
199
+ udp: {result: 'untested'},
200
+ tcp: {result: 'untested'},
201
+ xtls: {result: 'untested'},
202
+ }
203
+ );
204
+ }
205
+
206
+ const result: ClusterReachabilityResult = {
207
+ udp: {result: 'untested'},
208
+ tcp: {result: 'untested'},
209
+ xtls: {result: 'untested'},
210
+ };
211
+
212
+ // Get the first reachable UDP result from per-URL instances
213
+ for (const rpc of this.reachabilityPeerConnectionsForUdp) {
214
+ const rpcResult = rpc.getResult();
215
+ if (rpcResult.udp.result === 'reachable') {
216
+ result.udp = rpcResult.udp;
217
+ break;
218
+ }
219
+ if (rpcResult.udp.result === 'unreachable' && result.udp.result === 'untested') {
220
+ result.udp = rpcResult.udp;
221
+ }
222
+ }
223
+
224
+ // Get TCP and TLS results from the main peer connection
225
+ if (this.reachabilityPeerConnection) {
226
+ const mainResult = this.reachabilityPeerConnection.getResult();
227
+ result.tcp = mainResult.tcp;
228
+ result.xtls = mainResult.xtls;
229
+ }
230
+
231
+ return result;
117
232
  }
118
233
 
119
234
  /**
@@ -121,7 +236,17 @@ export class ClusterReachability extends EventsScope {
121
236
  * @returns {Promise<ClusterReachabilityResult>}
122
237
  */
123
238
  async start(): Promise<ClusterReachabilityResult> {
124
- await this.reachabilityPeerConnection.start();
239
+ const startPromises: Promise<ClusterReachabilityResult>[] = [];
240
+
241
+ this.reachabilityPeerConnectionsForUdp.forEach((rpc) => {
242
+ startPromises.push(rpc.start());
243
+ });
244
+
245
+ if (this.reachabilityPeerConnection) {
246
+ startPromises.push(this.reachabilityPeerConnection.start());
247
+ }
248
+
249
+ await Promise.all(startPromises);
125
250
 
126
251
  return this.getResult();
127
252
  }
@@ -131,6 +256,7 @@ export class ClusterReachability extends EventsScope {
131
256
  * @returns {void}
132
257
  */
133
258
  public abort() {
134
- this.reachabilityPeerConnection.abort();
259
+ this.reachabilityPeerConnectionsForUdp.forEach((rpc) => rpc.abort());
260
+ this.reachabilityPeerConnection?.abort();
135
261
  }
136
262
  }
@@ -961,7 +961,12 @@ export default class Reachability extends EventsScope {
961
961
  Object.keys(clusterList).forEach((key) => {
962
962
  const cluster = clusterList[key];
963
963
 
964
- this.clusterReachability[key] = new ClusterReachability(key, cluster);
964
+ this.clusterReachability[key] = new ClusterReachability(
965
+ key,
966
+ cluster,
967
+ // @ts-ignore
968
+ this.webex.config.meetings.enablePerUdpUrlReachability
969
+ );
965
970
  this.clusterReachability[key].on(Events.resultReady, async (data: ResultEventData) => {
966
971
  const {protocol, result, clientMediaIPs, latencyInMilliseconds} = data;
967
972
 
@@ -243,7 +243,9 @@ export class ReachabilityPeerConnection extends EventsScope {
243
243
  if (result.latencyInMilliseconds === undefined) {
244
244
  LoggerProxy.logger.log(
245
245
  // @ts-ignore
246
- `Reachability:ReachabilityPeerConnection#saveResult --> Successfully reached ${this.clusterName} over ${protocol}: ${latency}ms`
246
+ `Reachability:ReachabilityPeerConnection#saveResult --> Successfully reached ${
247
+ this.clusterName
248
+ } over ${protocol}: ${latency}ms, serverIp=${serverIp || 'unknown'}`
247
249
  );
248
250
  result.latencyInMilliseconds = latency;
249
251
  result.result = 'reachable';
@@ -1187,11 +1187,7 @@ describe('HashTreeParser', () => {
1187
1187
  method: 'POST',
1188
1188
  uri: `${mainDataSetUrl}/sync`,
1189
1189
  body: {
1190
- dataSet: {
1191
- name: 'main',
1192
- leafCount: 16,
1193
- root: '472801612a448c4e0ab74975ed9d7a2e'
1194
- },
1190
+ leafCount: 16,
1195
1191
  leafDataEntries: [
1196
1192
  {leafIndex: 0, elementIds: [{type: 'locus', id: 0, version: 201}]},
1197
1193
  {leafIndex: 4, elementIds: [{type: 'participant', id: 4, version: 301}]},
@@ -1244,11 +1240,7 @@ describe('HashTreeParser', () => {
1244
1240
  method: 'POST',
1245
1241
  uri: `${parser.dataSets.self.url}/sync`,
1246
1242
  body: {
1247
- dataSet: {
1248
- name: 'self',
1249
- leafCount: 1,
1250
- root: '483ba32a5db954720b4c43ed528d8075'
1251
- },
1243
+ leafCount: 1,
1252
1244
  leafDataEntries: [
1253
1245
  {leafIndex: 0, elementIds: [{type: 'self', id: 4, version: 102}]},
1254
1246
  ],
@@ -10,6 +10,7 @@ import {
10
10
  NatTypeUpdatedEventData,
11
11
  } from '@webex/plugin-meetings/src/reachability/clusterReachability';
12
12
  import {ReachabilityPeerConnection} from '@webex/plugin-meetings/src/reachability/reachabilityPeerConnection';
13
+ import {ReachabilityPeerConnectionEvents} from '@webex/plugin-meetings/src/reachability/reachability.types';
13
14
 
14
15
  describe('ClusterReachability', () => {
15
16
  let previousRTCPeerConnection;
@@ -92,6 +93,22 @@ describe('ClusterReachability', () => {
92
93
  assert.deepEqual(emittedEvents[Events.clientMediaIpsUpdated], []);
93
94
  });
94
95
 
96
+ it('should create separate peer connections when enablePerUdpUrlReachability is true', () => {
97
+ const perUdpClusterReachability = new ClusterReachability(
98
+ 'testName',
99
+ {
100
+ isVideoMesh: false,
101
+ udp: ['stun:udp1', 'stun:udp2'],
102
+ tcp: ['stun:tcp1.webex.com'],
103
+ xtls: ['stun:xtls1.webex.com'],
104
+ },
105
+ true
106
+ );
107
+
108
+ assert.equal((perUdpClusterReachability as any).reachabilityPeerConnectionsForUdp.length, 2);
109
+ assert.instanceOf((perUdpClusterReachability as any).reachabilityPeerConnection, ReachabilityPeerConnection);
110
+ });
111
+
95
112
  describe('#event relaying', () => {
96
113
  let clock;
97
114
 
@@ -172,6 +189,44 @@ describe('ClusterReachability', () => {
172
189
  clusterReachability.abort();
173
190
  await promise;
174
191
  });
192
+
193
+ it('emits only the first successful UDP result when enablePerUdpUrlReachability is true', async () => {
194
+ const perUdpClusterReachability = new ClusterReachability(
195
+ 'testName',
196
+ {
197
+ isVideoMesh: false,
198
+ udp: ['stun:udp1', 'stun:udp2'],
199
+ tcp: [],
200
+ xtls: [],
201
+ },
202
+ true
203
+ );
204
+
205
+ const udpEvents: ResultEventData[] = [];
206
+ perUdpClusterReachability.on(Events.resultReady, (data: ResultEventData) => {
207
+ udpEvents.push(data);
208
+ });
209
+
210
+ const udpRpc1 = (perUdpClusterReachability as any).reachabilityPeerConnectionsForUdp[0];
211
+ const udpRpc2 = (perUdpClusterReachability as any).reachabilityPeerConnectionsForUdp[1];
212
+
213
+ udpRpc1.emit({file: 'test', function: 'test'}, ReachabilityPeerConnectionEvents.resultReady, {
214
+ protocol: 'udp',
215
+ result: 'reachable',
216
+ latencyInMilliseconds: 50,
217
+ clientMediaIPs: ['1.1.1.1'],
218
+ });
219
+
220
+ udpRpc2.emit({file: 'test', function: 'test'}, ReachabilityPeerConnectionEvents.resultReady, {
221
+ protocol: 'udp',
222
+ result: 'reachable',
223
+ latencyInMilliseconds: 30,
224
+ clientMediaIPs: ['2.2.2.2'],
225
+ });
226
+
227
+ assert.equal(udpEvents.length, 1);
228
+ assert.equal(udpEvents[0].latencyInMilliseconds, 50);
229
+ });
175
230
  });
176
231
 
177
232
  describe('#subnet collection', () => {
@@ -236,6 +291,38 @@ describe('ClusterReachability', () => {
236
291
  assert.equal(clusterReachability.reachedSubnets.size, 3);
237
292
  assert.deepEqual(Array.from(clusterReachability.reachedSubnets), ['192.168.1.1', '10.0.0.1', '172.16.0.1']);
238
293
  });
294
+
295
+ it('collects reached subnets from all peer connections when enablePerUdpUrlReachability is true', async () => {
296
+ const perUdpClusterReachability = new ClusterReachability(
297
+ 'testName',
298
+ {
299
+ isVideoMesh: false,
300
+ udp: ['stun:udp1', 'stun:udp2'],
301
+ tcp: ['stun:tcp1.webex.com'],
302
+ xtls: [],
303
+ },
304
+ true
305
+ );
306
+
307
+ const udpRpc1 = (perUdpClusterReachability as any).reachabilityPeerConnectionsForUdp[0];
308
+ const udpRpc2 = (perUdpClusterReachability as any).reachabilityPeerConnectionsForUdp[1];
309
+ const tcpTlsRpc = (perUdpClusterReachability as any).reachabilityPeerConnection;
310
+
311
+ udpRpc1.emit({file: 'test', function: 'test'}, ReachabilityPeerConnectionEvents.reachedSubnets, {
312
+ subnets: ['192.168.1.1'],
313
+ });
314
+ udpRpc2.emit({file: 'test', function: 'test'}, ReachabilityPeerConnectionEvents.reachedSubnets, {
315
+ subnets: ['10.0.0.1'],
316
+ });
317
+ tcpTlsRpc.emit({file: 'test', function: 'test'}, ReachabilityPeerConnectionEvents.reachedSubnets, {
318
+ subnets: ['172.16.0.1'],
319
+ });
320
+
321
+ assert.equal(perUdpClusterReachability.reachedSubnets.size, 3);
322
+ assert.isTrue(perUdpClusterReachability.reachedSubnets.has('192.168.1.1'));
323
+ assert.isTrue(perUdpClusterReachability.reachedSubnets.has('10.0.0.1'));
324
+ assert.isTrue(perUdpClusterReachability.reachedSubnets.has('172.16.0.1'));
325
+ });
239
326
  });
240
327
 
241
328
  describe('#delegation', () => {
@@ -277,6 +364,43 @@ describe('ClusterReachability', () => {
277
364
  assert.calledOnce(rpcGetResultStub);
278
365
  assert.deepEqual(result, expectedResult);
279
366
  });
367
+
368
+ it('delegates start() and abort() to all peer connections when enablePerUdpUrlReachability is true', async () => {
369
+ const perUdpClusterReachability = new ClusterReachability(
370
+ 'testName',
371
+ {
372
+ isVideoMesh: false,
373
+ udp: ['stun:udp1', 'stun:udp2'],
374
+ tcp: ['stun:tcp1.webex.com'],
375
+ xtls: [],
376
+ },
377
+ true
378
+ );
379
+
380
+ const udpRpc1 = (perUdpClusterReachability as any).reachabilityPeerConnectionsForUdp[0];
381
+ const udpRpc2 = (perUdpClusterReachability as any).reachabilityPeerConnectionsForUdp[1];
382
+ const tcpTlsRpc = (perUdpClusterReachability as any).reachabilityPeerConnection;
383
+
384
+ const startStub1 = sinon.stub(udpRpc1, 'start').resolves({udp: {result: 'reachable'}});
385
+ const startStub2 = sinon.stub(udpRpc2, 'start').resolves({udp: {result: 'unreachable'}});
386
+ const startStubTcp = sinon.stub(tcpTlsRpc, 'start').resolves({tcp: {result: 'reachable'}});
387
+
388
+ const abortStub1 = sinon.stub(udpRpc1, 'abort');
389
+ const abortStub2 = sinon.stub(udpRpc2, 'abort');
390
+ const abortStubTcp = sinon.stub(tcpTlsRpc, 'abort');
391
+
392
+ await perUdpClusterReachability.start();
393
+
394
+ assert.calledOnce(startStub1);
395
+ assert.calledOnce(startStub2);
396
+ assert.calledOnce(startStubTcp);
397
+
398
+ perUdpClusterReachability.abort();
399
+
400
+ assert.calledOnce(abortStub1);
401
+ assert.calledOnce(abortStub2);
402
+ assert.calledOnce(abortStubTcp);
403
+ });
280
404
  });
281
405
 
282
406
  describe('#WebRTC peer connection setup', () => {
@@ -616,4 +740,4 @@ describe('ClusterReachability', () => {
616
740
  });
617
741
  });
618
742
  });
619
- });
743
+ });
@@ -1693,7 +1693,7 @@ describe('gatherReachability', () => {
1693
1693
  udp: ['testUDP1', 'testUDP2'],
1694
1694
  tcp: [], // empty list because TCP is disabled in config
1695
1695
  xtls: ['testXTLS1', 'testXTLS2'],
1696
- });
1696
+ }, undefined);
1697
1697
  });
1698
1698
 
1699
1699
  it('does not do TLS reachability if it is disabled in config', async () => {
@@ -1728,7 +1728,7 @@ describe('gatherReachability', () => {
1728
1728
  udp: ['testUDP1', 'testUDP2'],
1729
1729
  tcp: ['testTCP1', 'testTCP2'],
1730
1730
  xtls: [], // empty list because TLS is disabled in config
1731
- });
1731
+ }, undefined);
1732
1732
  });
1733
1733
 
1734
1734
  it('does not do TCP or TLS reachability if it is disabled in config', async () => {
@@ -1763,7 +1763,7 @@ describe('gatherReachability', () => {
1763
1763
  udp: ['testUDP1', 'testUDP2'],
1764
1764
  tcp: [], // empty list because TCP is disabled in config
1765
1765
  xtls: [], // empty list because TLS is disabled in config
1766
- });
1766
+ }, undefined);
1767
1767
  });
1768
1768
 
1769
1769
  it('retry of getClusters is succesfull', async () => {