@webex/plugin-meetings 3.8.1-next.2 → 3.8.1-next.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +26 -13
  2. package/dist/breakouts/breakout.js +1 -1
  3. package/dist/breakouts/index.js +1 -1
  4. package/dist/constants.js +2 -1
  5. package/dist/constants.js.map +1 -1
  6. package/dist/interpretation/index.js +1 -1
  7. package/dist/interpretation/siLanguage.js +1 -1
  8. package/dist/locus-info/index.js +35 -16
  9. package/dist/locus-info/index.js.map +1 -1
  10. package/dist/media/index.js +2 -2
  11. package/dist/media/index.js.map +1 -1
  12. package/dist/meeting/brbState.js +12 -12
  13. package/dist/meeting/brbState.js.map +1 -1
  14. package/dist/meeting/index.js +85 -78
  15. package/dist/meeting/index.js.map +1 -1
  16. package/dist/members/index.js +8 -6
  17. package/dist/members/index.js.map +1 -1
  18. package/dist/members/request.js +3 -3
  19. package/dist/members/request.js.map +1 -1
  20. package/dist/members/util.js +18 -6
  21. package/dist/members/util.js.map +1 -1
  22. package/dist/multistream/sendSlotManager.js +32 -2
  23. package/dist/multistream/sendSlotManager.js.map +1 -1
  24. package/dist/reachability/index.js +5 -10
  25. package/dist/reachability/index.js.map +1 -1
  26. package/dist/types/constants.d.ts +1 -0
  27. package/dist/types/meeting/brbState.d.ts +0 -1
  28. package/dist/types/meeting/index.d.ts +12 -3
  29. package/dist/types/members/index.d.ts +8 -3
  30. package/dist/types/members/request.d.ts +1 -1
  31. package/dist/types/members/util.d.ts +5 -2
  32. package/dist/types/multistream/sendSlotManager.d.ts +16 -0
  33. package/dist/types/reachability/index.d.ts +2 -2
  34. package/dist/webinar/index.js +1 -1
  35. package/package.json +24 -24
  36. package/src/constants.ts +1 -0
  37. package/src/locus-info/index.ts +46 -19
  38. package/src/media/index.ts +2 -2
  39. package/src/meeting/brbState.ts +8 -7
  40. package/src/meeting/index.ts +48 -32
  41. package/src/members/index.ts +7 -5
  42. package/src/members/request.ts +2 -2
  43. package/src/members/util.ts +14 -3
  44. package/src/multistream/sendSlotManager.ts +34 -2
  45. package/src/reachability/index.ts +5 -13
  46. package/test/unit/spec/locus-info/index.js +140 -44
  47. package/test/unit/spec/media/index.ts +107 -0
  48. package/test/unit/spec/meeting/brbState.ts +11 -9
  49. package/test/unit/spec/meeting/index.js +131 -39
  50. package/test/unit/spec/members/index.js +32 -9
  51. package/test/unit/spec/members/request.js +2 -2
  52. package/test/unit/spec/members/utils.js +27 -7
  53. package/test/unit/spec/multistream/sendSlotManager.ts +59 -0
  54. package/test/unit/spec/reachability/index.ts +2 -6
package/package.json CHANGED
@@ -43,13 +43,13 @@
43
43
  "@webex/eslint-config-legacy": "0.0.0",
44
44
  "@webex/jest-config-legacy": "0.0.0",
45
45
  "@webex/legacy-tools": "0.0.0",
46
- "@webex/plugin-meetings": "3.8.1-next.2",
47
- "@webex/plugin-rooms": "3.8.0-next.29",
48
- "@webex/test-helper-chai": "3.8.0-next.24",
49
- "@webex/test-helper-mocha": "3.8.0-next.24",
50
- "@webex/test-helper-mock-webex": "3.8.0-next.24",
51
- "@webex/test-helper-retry": "3.8.0-next.24",
52
- "@webex/test-helper-test-users": "3.8.0-next.24",
46
+ "@webex/plugin-meetings": "3.8.1-next.21",
47
+ "@webex/plugin-rooms": "3.8.1-next.4",
48
+ "@webex/test-helper-chai": "3.8.1-next.7",
49
+ "@webex/test-helper-mocha": "3.8.1-next.7",
50
+ "@webex/test-helper-mock-webex": "3.8.1-next.7",
51
+ "@webex/test-helper-retry": "3.8.1-next.7",
52
+ "@webex/test-helper-test-users": "3.8.1-next.7",
53
53
  "chai": "^4.3.4",
54
54
  "chai-as-promised": "^7.1.1",
55
55
  "eslint": "^8.24.0",
@@ -61,23 +61,23 @@
61
61
  "typescript": "^4.7.4"
62
62
  },
63
63
  "dependencies": {
64
- "@webex/common": "3.8.0-next.24",
65
- "@webex/event-dictionary-ts": "^1.0.1753",
66
- "@webex/internal-media-core": "2.17.1",
67
- "@webex/internal-plugin-conversation": "3.8.0-next.29",
68
- "@webex/internal-plugin-device": "3.8.0-next.24",
69
- "@webex/internal-plugin-llm": "3.8.0-next.27",
70
- "@webex/internal-plugin-mercury": "3.8.0-next.26",
71
- "@webex/internal-plugin-metrics": "3.8.0-next.24",
72
- "@webex/internal-plugin-support": "3.8.0-next.29",
73
- "@webex/internal-plugin-user": "3.8.0-next.24",
74
- "@webex/internal-plugin-voicea": "3.8.1-next.2",
75
- "@webex/media-helpers": "3.8.0-next.30",
76
- "@webex/plugin-people": "3.8.0-next.26",
77
- "@webex/plugin-rooms": "3.8.0-next.29",
64
+ "@webex/common": "3.8.1-next.7",
65
+ "@webex/event-dictionary-ts": "^1.0.1819",
66
+ "@webex/internal-media-core": "2.18.4",
67
+ "@webex/internal-plugin-conversation": "3.8.1-next.7",
68
+ "@webex/internal-plugin-device": "3.8.1-next.7",
69
+ "@webex/internal-plugin-llm": "3.8.1-next.7",
70
+ "@webex/internal-plugin-mercury": "3.8.1-next.7",
71
+ "@webex/internal-plugin-metrics": "3.8.1-next.7",
72
+ "@webex/internal-plugin-support": "3.8.1-next.7",
73
+ "@webex/internal-plugin-user": "3.8.1-next.7",
74
+ "@webex/internal-plugin-voicea": "3.8.1-next.21",
75
+ "@webex/media-helpers": "3.8.1-next.10",
76
+ "@webex/plugin-people": "3.8.1-next.7",
77
+ "@webex/plugin-rooms": "3.8.1-next.4",
78
78
  "@webex/ts-sdp": "^1.8.1",
79
- "@webex/web-capabilities": "^1.4.0",
80
- "@webex/webex-core": "3.8.0-next.24",
79
+ "@webex/web-capabilities": "^1.6.0",
80
+ "@webex/webex-core": "3.8.1-next.7",
81
81
  "ampersand-collection": "^2.0.2",
82
82
  "bowser": "^2.11.0",
83
83
  "btoa": "^1.2.1",
@@ -93,5 +93,5 @@
93
93
  "//": [
94
94
  "TODO: upgrade jwt-decode when moving to node 18"
95
95
  ],
96
- "version": "3.8.1-next.2"
96
+ "version": "3.8.1-next.21"
97
97
  }
package/src/constants.ts CHANGED
@@ -217,6 +217,7 @@ export const DIALER_REGEX = {
217
217
  PHONE_NUMBER:
218
218
  /^(?:(?:\+?1\s*(?:[.-]\s*)?)?(?:\(\s*([2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9])\s*\)|([2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9]))\s*(?:[.-]\s*)?)?([2-9]1[02-9]|[2-9][02-9]1|[2-9][02-9]{2})\s*(?:[.-]\s*)?([0-9]{4})(?:\s*(?:#|x\.?|ext\.?|extension)\s*(\d+))?$/,
219
219
  E164_FORMAT: /^\+[1-9]\d{1,14}$/,
220
+ INTERNAL_NUMBER: /^\d{1,14}$/,
220
221
  };
221
222
 
222
223
  // eslint-disable-next-line max-len
@@ -99,6 +99,7 @@ export default class LocusInfo extends EventsScope {
99
99
  private doLocusSync(meeting: any) {
100
100
  let isDelta;
101
101
  let url;
102
+ let meetingDestroyed = false;
102
103
 
103
104
  if (this.locusParser.workingCopy.syncUrl) {
104
105
  url = this.locusParser.workingCopy.syncUrl;
@@ -134,32 +135,56 @@ export default class LocusInfo extends EventsScope {
134
135
 
135
136
  isDelta = false;
136
137
 
137
- return meeting.meetingRequest.getLocusDTO({url: meeting.locusUrl}).catch((err) => {
138
- LoggerProxy.logger.info(
139
- 'Locus-info:index#doLocusSync --> fallback full sync failed, destroying the meeting'
140
- );
141
- this.webex.meetings.destroy(meeting, MEETING_REMOVED_REASON.LOCUS_DTO_SYNC_FAILED);
142
- throw err;
143
- });
138
+ // Locus sometimes returns 403, for example if meeting has ended, no point trying the fallback to full sync in that case
139
+ if (e.statusCode !== 403) {
140
+ return meeting.meetingRequest.getLocusDTO({url: meeting.locusUrl}).catch((err) => {
141
+ LoggerProxy.logger.info(
142
+ 'Locus-info:index#doLocusSync --> fallback full sync failed, destroying the meeting'
143
+ );
144
+ this.webex.meetings.destroy(meeting, MEETING_REMOVED_REASON.LOCUS_DTO_SYNC_FAILED);
145
+ meetingDestroyed = true;
146
+ throw err;
147
+ });
148
+ }
149
+ LoggerProxy.logger.info(
150
+ 'Locus-info:index#doLocusSync --> got 403 from Locus, skipping fallback to full sync, destroying the meeting'
151
+ );
152
+ } else {
153
+ LoggerProxy.logger.info(
154
+ 'Locus-info:index#doLocusSync --> fallback full sync failed, destroying the meeting'
155
+ );
144
156
  }
145
- LoggerProxy.logger.info(
146
- 'Locus-info:index#doLocusSync --> fallback full sync failed, destroying the meeting'
147
- );
148
157
  this.webex.meetings.destroy(meeting, MEETING_REMOVED_REASON.LOCUS_DTO_SYNC_FAILED);
158
+ meetingDestroyed = true;
149
159
  throw e;
150
160
  })
151
161
  .then((res) => {
152
- if (isDelta) {
153
- if (!isEmpty(res.body)) {
154
- meeting.locusInfo.handleLocusDelta(res.body, meeting);
155
- } else {
162
+ if (isEmpty(res.body)) {
163
+ if (isDelta) {
156
164
  LoggerProxy.logger.info(
157
165
  'Locus-info:index#doLocusSync --> received empty body from syncUrl, so we already have latest Locus DTO'
158
166
  );
167
+ } else {
168
+ LoggerProxy.logger.info(
169
+ 'Locus-info:index#doLocusSync --> received empty body from full DTO sync request'
170
+ );
159
171
  }
160
- } else {
161
- meeting.locusInfo.onFullLocus(res.body);
172
+
173
+ return;
174
+ }
175
+
176
+ if (isDelta) {
177
+ if (res.body.baseSequence) {
178
+ meeting.locusInfo.handleLocusDelta(res.body, meeting);
179
+
180
+ return;
181
+ }
182
+ // in some cases Locus might return us full DTO even when we asked for a delta
183
+ LoggerProxy.logger.info(
184
+ 'Locus-info:index#doLocusSync --> got full DTO when we asked for delta'
185
+ );
162
186
  }
187
+ meeting.locusInfo.onFullLocus(res.body);
163
188
  })
164
189
  .catch((e) => {
165
190
  LoggerProxy.logger.info(
@@ -176,9 +201,11 @@ export default class LocusInfo extends EventsScope {
176
201
  });
177
202
  })
178
203
  .finally(() => {
179
- // Notify parser to resume processing delta events.
180
- // Any deltas in the queue that have now been superseded by this sync will simply be ignored
181
- this.locusParser.resume();
204
+ if (!meetingDestroyed) {
205
+ // Notify parser to resume processing delta events.
206
+ // Any deltas in the queue that have now been superseded by this sync will simply be ignored
207
+ this.locusParser.resume();
208
+ }
182
209
  });
183
210
  }
184
211
 
@@ -239,8 +239,8 @@ Media.createMediaConnection = (
239
239
  screenShareAudio: shareAudioStream?.outputStream?.getTracks()[0], // TODO: add type for screenShareAudio in internal-media-core SPARK-446923
240
240
  } as unknown,
241
241
  direction: {
242
- audio: Media.getDirection(true, mediaDirection.receiveAudio, mediaDirection.sendAudio),
243
- video: Media.getDirection(true, mediaDirection.receiveVideo, mediaDirection.sendVideo),
242
+ audio: Media.getDirection(false, mediaDirection.receiveAudio, mediaDirection.sendAudio),
243
+ video: Media.getDirection(false, mediaDirection.receiveVideo, mediaDirection.sendVideo),
244
244
  screenShareVideo: Media.getDirection(
245
245
  false,
246
246
  mediaDirection.receiveShare,
@@ -58,7 +58,12 @@ export class BrbState {
58
58
  public enable(enabled: boolean, sendSlotManager: SendSlotManager) {
59
59
  this.state.client.enabled = enabled;
60
60
 
61
- return this.applyClientStateToServer(sendSlotManager);
61
+ return this.applyClientStateToServer(sendSlotManager).finally(() => {
62
+ sendSlotManager.setSourceStateOverride(
63
+ MediaType.VideoMain,
64
+ this.state.client.enabled ? 'away' : null
65
+ );
66
+ });
62
67
  }
63
68
 
64
69
  /**
@@ -92,7 +97,7 @@ export class BrbState {
92
97
 
93
98
  this.state.syncToServerInProgress = true;
94
99
 
95
- return this.sendLocalBrbStateToServer(sendSlotManager)
100
+ return this.sendLocalBrbStateToServer()
96
101
  .then(() => {
97
102
  this.state.syncToServerInProgress = false;
98
103
 
@@ -120,10 +125,9 @@ export class BrbState {
120
125
  /**
121
126
  * Send the local brb state to the server
122
127
  *
123
- * @param {SendSlotManager} sendSlotManager
124
128
  * @returns {Promise}
125
129
  */
126
- private async sendLocalBrbStateToServer(sendSlotManager: SendSlotManager) {
130
+ private async sendLocalBrbStateToServer() {
127
131
  const {enabled} = this.state.client;
128
132
 
129
133
  if (!this.meeting.isMultistream) {
@@ -153,9 +157,6 @@ export class BrbState {
153
157
  deviceUrl: this.meeting.deviceUrl,
154
158
  selfId: this.meeting.selfId,
155
159
  })
156
- .then(() => {
157
- sendSlotManager.setSourceStateOverride(MediaType.VideoMain, enabled ? 'away' : null);
158
- })
159
160
  .catch((error) => {
160
161
  LoggerProxy.logger.error('Meeting:brbState#sendLocalBrbStateToServer: Error ', error);
161
162
 
@@ -231,6 +231,14 @@ export type AddMediaOptions = {
231
231
  remoteMediaManagerConfig?: RemoteMediaManagerConfiguration; // applies only to multistream meetings
232
232
  bundlePolicy?: BundlePolicy; // applies only to multistream meetings
233
233
  allowMediaInLobby?: boolean; // allows adding media when in the lobby
234
+ additionalMediaOptions?: AdditionalMediaOptions; // allows adding additional options like send/receive audio/video
235
+ };
236
+
237
+ export type AdditionalMediaOptions = {
238
+ sendVideo?: boolean; // if not specified, default value of videoEnabled is used
239
+ receiveVideo?: boolean; // if not specified, default value of videoEnabled is used
240
+ sendAudio?: boolean; // if not specified, default value of audioEnabled true is used
241
+ receiveAudio?: boolean; // if not specified, default value of audioEnabled true is used
234
242
  };
235
243
 
236
244
  export type CallStateForMetrics = {
@@ -263,8 +271,9 @@ type FetchMeetingInfoParams = {
263
271
  };
264
272
 
265
273
  type MediaReachabilityMetrics = ReachabilityMetrics & {
266
- isSubnetReachable: boolean;
267
- selectedCluster: string | null;
274
+ subnet_reachable: boolean;
275
+ selected_cluster: string | null;
276
+ selected_subnet: string | null;
268
277
  };
269
278
 
270
279
  /**
@@ -2757,9 +2766,11 @@ export default class Meeting extends StatelessWebexPlugin {
2757
2766
  LOCUSINFO.EVENTS.CONTROLS_MEETING_TRANSCRIPTION_SPOKEN_LANGUAGE_UPDATED,
2758
2767
  ({spokenLanguage}) => {
2759
2768
  if (spokenLanguage) {
2760
- this.transcription.languageOptions.currentSpokenLanguage = spokenLanguage;
2769
+ if (this.transcription?.languageOptions) {
2770
+ this.transcription.languageOptions.currentSpokenLanguage = spokenLanguage;
2771
+ }
2761
2772
  // @ts-ignore
2762
- this.webex.internal.voicea.onSpokenLanguageUpdate(spokenLanguage);
2773
+ this.webex.internal.voicea.onSpokenLanguageUpdate(spokenLanguage, this.id);
2763
2774
 
2764
2775
  Trigger.trigger(
2765
2776
  this,
@@ -2768,7 +2779,7 @@ export default class Meeting extends StatelessWebexPlugin {
2768
2779
  function: 'setupLocusControlsListener',
2769
2780
  },
2770
2781
  EVENT_TRIGGERS.MEETING_TRANSCRIPTION_SPOKEN_LANGUAGE_UPDATED,
2771
- {spokenLanguage}
2782
+ {spokenLanguage, meetingId: this.id}
2772
2783
  );
2773
2784
  }
2774
2785
  }
@@ -3850,15 +3861,16 @@ export default class Meeting extends StatelessWebexPlugin {
3850
3861
  }
3851
3862
 
3852
3863
  /**
3853
- * Cancel an SIP call invitation made during a meeting
3864
+ * Cancel an SIP/phone call invitation made during a meeting
3854
3865
  * @param {Object} invitee
3855
3866
  * @param {String} invitee.memberId
3856
- * @returns {Promise} see #members.cancelSIPInvite
3867
+ * @param {Boolean} [invitee.isInternalNumber] - When cancel phone invitation, if the number is internal
3868
+ * @returns {Promise} see #members.cancelInviteByMemberId
3857
3869
  * @public
3858
3870
  * @memberof Meeting
3859
3871
  */
3860
- public cancelSIPInvite(invitee: {memberId: string}) {
3861
- return this.members.cancelSIPInvite(invitee);
3872
+ public cancelInviteByMemberId(invitee: {memberId: string; isInternalNumber?: boolean}) {
3873
+ return this.members.cancelInviteByMemberId(invitee);
3862
3874
  }
3863
3875
 
3864
3876
  /**
@@ -4915,11 +4927,6 @@ export default class Meeting extends StatelessWebexPlugin {
4915
4927
 
4916
4928
  // Only send restore event when it was disconnected before and for connected later
4917
4929
  if (!this.hasWebsocketConnected) {
4918
- // @ts-ignore
4919
- this.webex.internal.newMetrics.submitClientEvent({
4920
- name: 'client.mercury.connection.restored',
4921
- options: {meetingId: this.id},
4922
- });
4923
4930
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MERCURY_CONNECTION_RESTORED, {
4924
4931
  correlation_id: this.correlationId,
4925
4932
  });
@@ -4930,11 +4937,6 @@ export default class Meeting extends StatelessWebexPlugin {
4930
4937
  // @ts-ignore
4931
4938
  this.webex.internal.mercury.on(OFFLINE, () => {
4932
4939
  LoggerProxy.logger.error('Meeting:index#setMercuryListener --> Web socket offline');
4933
- // @ts-ignore
4934
- this.webex.internal.newMetrics.submitClientEvent({
4935
- name: 'client.mercury.connection.lost',
4936
- options: {meetingId: this.id},
4937
- });
4938
4940
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MERCURY_CONNECTION_FAILURE, {
4939
4941
  correlation_id: this.correlationId,
4940
4942
  });
@@ -7764,8 +7766,21 @@ export default class Meeting extends StatelessWebexPlugin {
7764
7766
  shareVideoEnabled = true,
7765
7767
  remoteMediaManagerConfig,
7766
7768
  bundlePolicy = 'max-bundle',
7769
+ additionalMediaOptions = {},
7767
7770
  } = options;
7768
7771
 
7772
+ const {
7773
+ sendVideo: rawSendVideo,
7774
+ receiveVideo: rawReceiveVideo,
7775
+ sendAudio: rawSendAudio,
7776
+ receiveAudio: rawReceiveAudio,
7777
+ } = additionalMediaOptions;
7778
+
7779
+ const sendVideo = videoEnabled && (rawSendVideo ?? true);
7780
+ const receiveVideo = videoEnabled && (rawReceiveVideo ?? true);
7781
+ const sendAudio = audioEnabled && (rawSendAudio ?? true);
7782
+ const receiveAudio = audioEnabled && (rawReceiveAudio ?? true);
7783
+
7769
7784
  this.allowMediaInLobby = options?.allowMediaInLobby;
7770
7785
 
7771
7786
  // If the user is unjoined or guest waiting in lobby dont allow the user to addMedia
@@ -7801,11 +7816,11 @@ export default class Meeting extends StatelessWebexPlugin {
7801
7816
  // when audioEnabled/videoEnabled is true, we set sendAudio/sendVideo to true even before any streams are published
7802
7817
  // to avoid doing an extra SDP exchange when they are published for the first time
7803
7818
  this.mediaProperties.setMediaDirection({
7804
- sendAudio: audioEnabled,
7805
- sendVideo: videoEnabled,
7819
+ sendAudio,
7820
+ sendVideo,
7806
7821
  sendShare: false,
7807
- receiveAudio: audioEnabled,
7808
- receiveVideo: videoEnabled,
7822
+ receiveAudio,
7823
+ receiveVideo,
7809
7824
  receiveShare: shareAudioEnabled || shareVideoEnabled,
7810
7825
  });
7811
7826
 
@@ -9731,21 +9746,22 @@ export default class Meeting extends StatelessWebexPlugin {
9731
9746
  return total;
9732
9747
  }, 0);
9733
9748
 
9749
+ const selectedSubnetFirstOctet = this.mediaServerIp?.split('.')[0];
9750
+
9734
9751
  let isSubnetReachable = null;
9735
- if (totalSuccessCases > 0) {
9736
- // @ts-ignore
9737
- isSubnetReachable = this.webex.meetings.reachability.isSubnetReachable(this.mediaServerIp);
9752
+ if (totalSuccessCases > 0 && selectedSubnetFirstOctet) {
9753
+ isSubnetReachable =
9754
+ // @ts-ignore
9755
+ this.webex.meetings.reachability.isSubnetReachable(selectedSubnetFirstOctet);
9738
9756
  }
9739
9757
 
9740
- let selectedCluster = null;
9741
- if (this.mediaConnections && this.mediaConnections.length > 0) {
9742
- selectedCluster = this.mediaConnections[0].mediaAgentCluster;
9743
- }
9758
+ const selectedCluster = this.mediaConnections?.[0]?.mediaAgentCluster ?? null;
9744
9759
 
9745
9760
  return {
9746
9761
  ...reachabilityMetrics,
9747
- isSubnetReachable,
9748
- selectedCluster,
9762
+ subnet_reachable: isSubnetReachable,
9763
+ selected_cluster: selectedCluster,
9764
+ selected_subnet: selectedSubnetFirstOctet ? `${selectedSubnetFirstOctet}.X.X.X` : null,
9749
9765
  };
9750
9766
  }
9751
9767
  }
@@ -846,12 +846,14 @@ export default class Members extends StatelessWebexPlugin {
846
846
  }
847
847
 
848
848
  /**
849
- * Cancels an SIP call to the associated meeting
850
- * @param {String} invitee
849
+ * Cancels an SIP/phone call to the associated meeting
850
+ * @param {Object} invitee
851
+ * @param {String} invitee.memberId - The memberId of the invitee
852
+ * @param {Boolean} [invitee.isInternalNumber] - When cancel phone invitation, if the number is internal
851
853
  * @returns {Promise}
852
854
  * @memberof Members
853
855
  */
854
- cancelSIPInvite(invitee: any) {
856
+ cancelInviteByMemberId(invitee: {memberId: string; isInternalNumber?: boolean}) {
855
857
  if (!this.locusUrl) {
856
858
  return Promise.reject(
857
859
  new ParameterError('The associated locus url for this meeting object must be defined.')
@@ -862,9 +864,9 @@ export default class Members extends StatelessWebexPlugin {
862
864
  new ParameterError('The invitee must be defined with a memberId property.')
863
865
  );
864
866
  }
865
- const options = MembersUtil.cancelSIPInviteOptions(invitee, this.locusUrl);
867
+ const options = MembersUtil.cancelInviteByMemberIdOptions(invitee, this.locusUrl);
866
868
 
867
- return this.membersRequest.cancelSIPInvite(options);
869
+ return this.membersRequest.cancelInviteByMemberId(options);
868
870
  }
869
871
 
870
872
  /**
@@ -285,14 +285,14 @@ export default class MembersRequest extends StatelessWebexPlugin {
285
285
  * @throws {Error} if the options are not valid and complete, must have invitee with memberId AND locusUrl
286
286
  * @memberof MembersRequest
287
287
  */
288
- cancelSIPInvite(options: any) {
288
+ cancelInviteByMemberId(options: any) {
289
289
  if (!options?.invitee?.memberId || !options?.locusUrl) {
290
290
  throw new ParameterError(
291
291
  'invitee must be passed and the associated locus url for this meeting object must be defined.'
292
292
  );
293
293
  }
294
294
 
295
- const requestParams = MembersUtil.generateCancelSIPInviteRequestParams(options);
295
+ const requestParams = MembersUtil.generateCancelInviteByMemberIdRequestParams(options);
296
296
 
297
297
  return this.locusDeltaRequest(requestParams);
298
298
  }
@@ -1,4 +1,5 @@
1
1
  import uuid from 'uuid';
2
+ import {has} from 'lodash';
2
3
  import {
3
4
  HTTP_VERBS,
4
5
  CONTROLS,
@@ -47,6 +48,9 @@ const MembersUtil = {
47
48
  address:
48
49
  options.invitee.emailAddress || options.invitee.email || options.invitee.phoneNumber,
49
50
  ...(options.invitee.roles ? {roles: options.invitee.roles} : {}),
51
+ ...(has(options.invitee, 'isInternalNumber')
52
+ ? {isInternalNumber: options.invitee.isInternalNumber}
53
+ : {}),
50
54
  },
51
55
  ],
52
56
  alertIfActive: options.alertIfActive,
@@ -107,6 +111,10 @@ const MembersUtil = {
107
111
  }
108
112
 
109
113
  if (invitee.phoneNumber) {
114
+ if (invitee.isInternalNumber) {
115
+ return !DIALER_REGEX.INTERNAL_NUMBER.test(invitee.phoneNumber);
116
+ }
117
+
110
118
  return !DIALER_REGEX.E164_FORMAT.test(invitee.phoneNumber);
111
119
  }
112
120
 
@@ -371,17 +379,20 @@ const MembersUtil = {
371
379
  return requestParams;
372
380
  },
373
381
 
374
- cancelSIPInviteOptions: (invitee, locusUrl) => ({
382
+ cancelInviteByMemberIdOptions: (invitee, locusUrl) => ({
375
383
  invitee,
376
384
  locusUrl,
377
385
  }),
378
386
 
379
- generateCancelSIPInviteRequestParams: (options) => {
387
+ generateCancelInviteByMemberIdRequestParams: (options) => {
388
+ const {memberId, isInternalNumber} = options.invitee;
389
+ const hasIsInternalNumberProp = has(options.invitee, 'isInternalNumber');
380
390
  const body = {
381
391
  actionType: _REMOVE_,
382
392
  invitees: [
383
393
  {
384
- address: options.invitee.memberId,
394
+ address: memberId,
395
+ ...(hasIsInternalNumberProp ? {isInternalNumber} : {}),
385
396
  },
386
397
  ],
387
398
  };
@@ -7,10 +7,20 @@ import {
7
7
  StreamState,
8
8
  } from '@webex/internal-media-core';
9
9
 
10
+ /**
11
+ * This class is used to manage the sendSlots for the given media types.
12
+ */
10
13
  export default class SendSlotManager {
11
14
  private readonly slots: Map<MediaType, SendSlot> = new Map();
12
15
  private readonly LoggerProxy: any;
16
+ private readonly sourceStateOverrides: Map<MediaType, StreamState> = new Map();
13
17
 
18
+ /**
19
+ * Constructor for SendSlotManager
20
+ *
21
+ * @param {any} LoggerProxy is used to log the messages
22
+ * @constructor
23
+ */
14
24
  constructor(LoggerProxy: any) {
15
25
  this.LoggerProxy = LoggerProxy;
16
26
  }
@@ -93,7 +103,7 @@ export default class SendSlotManager {
93
103
  public setSourceStateOverride(mediaType: MediaType, state: StreamState | null) {
94
104
  if (mediaType !== MediaType.VideoMain) {
95
105
  throw new Error(
96
- `sendSlotManager cannot set source state override which media type is ${mediaType}`
106
+ `Invalid media type '${mediaType}'. Source state overrides are only applicable to ${MediaType.VideoMain}.`
97
107
  );
98
108
  }
99
109
 
@@ -103,17 +113,39 @@ export default class SendSlotManager {
103
113
  throw new Error(`Slot for ${mediaType} does not exist`);
104
114
  }
105
115
 
116
+ const currentStateOverride = this.getSourceStateOverride(mediaType);
117
+ if (currentStateOverride === state) {
118
+ return;
119
+ }
120
+
106
121
  if (state) {
107
122
  slot.setSourceStateOverride(state);
123
+ this.sourceStateOverrides.set(mediaType, state);
108
124
  } else {
109
125
  slot.clearSourceStateOverride();
126
+ this.sourceStateOverrides.delete(mediaType);
110
127
  }
111
128
 
112
129
  this.LoggerProxy.logger.info(
113
- `SendSlotsManager->setSourceStateOverride#set source state override for ${mediaType} to ${state}`
130
+ `SendSlotManager->setSourceStateOverride#set source state override for ${mediaType} to ${state}`
114
131
  );
115
132
  }
116
133
 
134
+ /**
135
+ * Gets the source state override for the given media type.
136
+ * @param {MediaType} mediaType - The type of media to get the source state override for.
137
+ * @returns {StreamState | null} - The current source state override or null if not set.
138
+ */
139
+ private getSourceStateOverride(mediaType: MediaType): StreamState | null {
140
+ if (mediaType !== MediaType.VideoMain) {
141
+ throw new Error(
142
+ `Invalid media type '${mediaType}'. Source state overrides are only applicable to ${MediaType.VideoMain}.`
143
+ );
144
+ }
145
+
146
+ return this.sourceStateOverrides.get(mediaType) || null;
147
+ }
148
+
117
149
  /**
118
150
  * This method publishes the given stream to the sendSlot for the given mediaType
119
151
  * @param {MediaType} mediaType MediaType of the sendSlot to which a stream needs to be published (AUDIO_MAIN/VIDEO_MAIN/AUDIO_SLIDES/VIDEO_SLIDES)
@@ -140,22 +140,14 @@ export default class Reachability extends EventsScope {
140
140
 
141
141
  /**
142
142
  * Checks if the given subnet is reachable
143
- * @param {string} mediaServerIp - media server ip
143
+ * @param {string} selectedSubnetFirstOctet - selected subnet first octet, e.g. "10" for "10.X.X.X"
144
144
  * @returns {boolean | null} true if reachable, false if not reachable, null if mediaServerIp is not provided
145
145
  * @public
146
146
  * @memberof Reachability
147
147
  */
148
- public isSubnetReachable(mediaServerIp?: string): boolean | null {
149
- if (!mediaServerIp) {
150
- LoggerProxy.logger.error(`Reachability:index#isSubnetReachable --> mediaServerIp is null`);
151
-
152
- return null;
153
- }
154
-
155
- const subnetFirstOctet = mediaServerIp.split('.')[0];
156
-
148
+ public isSubnetReachable(selectedSubnetFirstOctet: string): boolean | null {
157
149
  LoggerProxy.logger.info(
158
- `Reachability:index#isSubnetReachable --> Looking for subnet: ${subnetFirstOctet}.X.X.X`
150
+ `Reachability:index#isSubnetReachable --> Looking for subnet: ${selectedSubnetFirstOctet}.X.X.X`
159
151
  );
160
152
 
161
153
  const matchingReachedClusters = Object.values(this.clusterReachability).reduce(
@@ -167,7 +159,7 @@ export default class Reachability extends EventsScope {
167
159
  const subnet = reachedSubnetsArray[i];
168
160
  const reachedSubnetFirstOctet = subnet.split('.')[0];
169
161
 
170
- if (subnetFirstOctet === reachedSubnetFirstOctet) {
162
+ if (selectedSubnetFirstOctet === reachedSubnetFirstOctet) {
171
163
  acc.add(cluster.name);
172
164
  }
173
165
 
@@ -186,7 +178,7 @@ export default class Reachability extends EventsScope {
186
178
  );
187
179
 
188
180
  LoggerProxy.logger.info(
189
- `Reachability:index#isSubnetReachable --> Found ${matchingReachedClusters.size} clusters that use the subnet ${subnetFirstOctet}.X.X.X`
181
+ `Reachability:index#isSubnetReachable --> Found ${matchingReachedClusters.size} clusters that use the subnet ${selectedSubnetFirstOctet}.X.X.X`
190
182
  );
191
183
 
192
184
  return matchingReachedClusters.size > 0;