@webex/plugin-meetings 3.10.0 → 3.11.0-next.10

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 (299) hide show
  1. package/dist/annotation/annotation.types.js.map +1 -1
  2. package/dist/annotation/constants.js.map +1 -1
  3. package/dist/annotation/index.js +19 -22
  4. package/dist/annotation/index.js.map +1 -1
  5. package/dist/breakouts/breakout.js +6 -6
  6. package/dist/breakouts/breakout.js.map +1 -1
  7. package/dist/breakouts/collection.js.map +1 -1
  8. package/dist/breakouts/edit-lock-error.js +9 -11
  9. package/dist/breakouts/edit-lock-error.js.map +1 -1
  10. package/dist/breakouts/events.js.map +1 -1
  11. package/dist/breakouts/index.js +126 -127
  12. package/dist/breakouts/index.js.map +1 -1
  13. package/dist/breakouts/request.js +6 -8
  14. package/dist/breakouts/request.js.map +1 -1
  15. package/dist/breakouts/utils.js.map +1 -1
  16. package/dist/common/browser-detection.js.map +1 -1
  17. package/dist/common/collection.js +1 -2
  18. package/dist/common/collection.js.map +1 -1
  19. package/dist/common/config.js.map +1 -1
  20. package/dist/common/errors/captcha-error.js +9 -11
  21. package/dist/common/errors/captcha-error.js.map +1 -1
  22. package/dist/common/errors/intent-to-join.js +10 -12
  23. package/dist/common/errors/intent-to-join.js.map +1 -1
  24. package/dist/common/errors/join-forbidden-error.js +10 -12
  25. package/dist/common/errors/join-forbidden-error.js.map +1 -1
  26. package/dist/common/errors/join-meeting.js +10 -12
  27. package/dist/common/errors/join-meeting.js.map +1 -1
  28. package/dist/common/errors/join-webinar-error.js +9 -11
  29. package/dist/common/errors/join-webinar-error.js.map +1 -1
  30. package/dist/common/errors/media.js +9 -11
  31. package/dist/common/errors/media.js.map +1 -1
  32. package/dist/common/errors/multistream-not-supported-error.js +9 -11
  33. package/dist/common/errors/multistream-not-supported-error.js.map +1 -1
  34. package/dist/common/errors/no-meeting-info.js +9 -11
  35. package/dist/common/errors/no-meeting-info.js.map +1 -1
  36. package/dist/common/errors/parameter.js +11 -14
  37. package/dist/common/errors/parameter.js.map +1 -1
  38. package/dist/common/errors/password-error.js +9 -11
  39. package/dist/common/errors/password-error.js.map +1 -1
  40. package/dist/common/errors/permission.js +9 -11
  41. package/dist/common/errors/permission.js.map +1 -1
  42. package/dist/common/errors/reclaim-host-role-errors.js +32 -38
  43. package/dist/common/errors/reclaim-host-role-errors.js.map +1 -1
  44. package/dist/common/errors/reconnection-not-started.js +5 -6
  45. package/dist/common/errors/reconnection-not-started.js.map +1 -1
  46. package/dist/common/errors/reconnection.js +9 -11
  47. package/dist/common/errors/reconnection.js.map +1 -1
  48. package/dist/common/errors/stats.js +9 -11
  49. package/dist/common/errors/stats.js.map +1 -1
  50. package/dist/common/errors/webex-errors.js +38 -27
  51. package/dist/common/errors/webex-errors.js.map +1 -1
  52. package/dist/common/errors/webex-meetings-error.js +9 -12
  53. package/dist/common/errors/webex-meetings-error.js.map +1 -1
  54. package/dist/common/events/events-scope.js +9 -10
  55. package/dist/common/events/events-scope.js.map +1 -1
  56. package/dist/common/events/events.js +9 -10
  57. package/dist/common/events/events.js.map +1 -1
  58. package/dist/common/events/trigger-proxy.js.map +1 -1
  59. package/dist/common/events/util.js.map +1 -1
  60. package/dist/common/logs/logger-config.js.map +1 -1
  61. package/dist/common/logs/logger-proxy.js.map +1 -1
  62. package/dist/common/logs/request.js +17 -17
  63. package/dist/common/logs/request.js.map +1 -1
  64. package/dist/common/queue.js +1 -2
  65. package/dist/common/queue.js.map +1 -1
  66. package/dist/config.js +2 -2
  67. package/dist/config.js.map +1 -1
  68. package/dist/constants.js +14 -8
  69. package/dist/constants.js.map +1 -1
  70. package/dist/controls-options-manager/constants.js.map +1 -1
  71. package/dist/controls-options-manager/enums.js.map +1 -1
  72. package/dist/controls-options-manager/index.js +1 -2
  73. package/dist/controls-options-manager/index.js.map +1 -1
  74. package/dist/controls-options-manager/types.js.map +1 -1
  75. package/dist/controls-options-manager/util.js +1 -2
  76. package/dist/controls-options-manager/util.js.map +1 -1
  77. package/dist/hashTree/constants.js +20 -0
  78. package/dist/hashTree/constants.js.map +1 -0
  79. package/dist/hashTree/hashTree.js +515 -0
  80. package/dist/hashTree/hashTree.js.map +1 -0
  81. package/dist/hashTree/hashTreeParser.js +1250 -0
  82. package/dist/hashTree/hashTreeParser.js.map +1 -0
  83. package/dist/hashTree/types.js +23 -0
  84. package/dist/hashTree/types.js.map +1 -0
  85. package/dist/hashTree/utils.js +59 -0
  86. package/dist/hashTree/utils.js.map +1 -0
  87. package/dist/index.js +8 -2
  88. package/dist/index.js.map +1 -1
  89. package/dist/interceptors/index.js.map +1 -1
  90. package/dist/interceptors/locusRetry.js +6 -8
  91. package/dist/interceptors/locusRetry.js.map +1 -1
  92. package/dist/interceptors/locusRouteToken.js +33 -13
  93. package/dist/interceptors/locusRouteToken.js.map +1 -1
  94. package/dist/interpretation/collection.js.map +1 -1
  95. package/dist/interpretation/index.js +1 -2
  96. package/dist/interpretation/index.js.map +1 -1
  97. package/dist/interpretation/siLanguage.js +1 -1
  98. package/dist/interpretation/siLanguage.js.map +1 -1
  99. package/dist/locus-info/controlsUtils.js +5 -3
  100. package/dist/locus-info/controlsUtils.js.map +1 -1
  101. package/dist/locus-info/embeddedAppsUtils.js.map +1 -1
  102. package/dist/locus-info/fullState.js.map +1 -1
  103. package/dist/locus-info/hostUtils.js.map +1 -1
  104. package/dist/locus-info/index.js +619 -177
  105. package/dist/locus-info/index.js.map +1 -1
  106. package/dist/locus-info/infoUtils.js.map +1 -1
  107. package/dist/locus-info/mediaSharesUtils.js.map +1 -1
  108. package/dist/locus-info/parser.js +3 -4
  109. package/dist/locus-info/parser.js.map +1 -1
  110. package/dist/locus-info/selfUtils.js.map +1 -1
  111. package/dist/locus-info/types.js +7 -0
  112. package/dist/locus-info/types.js.map +1 -0
  113. package/dist/media/MediaConnectionAwaiter.js +58 -3
  114. package/dist/media/MediaConnectionAwaiter.js.map +1 -1
  115. package/dist/media/index.js +5 -2
  116. package/dist/media/index.js.map +1 -1
  117. package/dist/media/properties.js +19 -19
  118. package/dist/media/properties.js.map +1 -1
  119. package/dist/media/util.js.map +1 -1
  120. package/dist/meeting/brbState.js +8 -9
  121. package/dist/meeting/brbState.js.map +1 -1
  122. package/dist/meeting/connectionStateHandler.js +10 -13
  123. package/dist/meeting/connectionStateHandler.js.map +1 -1
  124. package/dist/meeting/in-meeting-actions.js.map +1 -1
  125. package/dist/meeting/index.js +1672 -1553
  126. package/dist/meeting/index.js.map +1 -1
  127. package/dist/meeting/locusMediaRequest.js +13 -17
  128. package/dist/meeting/locusMediaRequest.js.map +1 -1
  129. package/dist/meeting/muteState.js +11 -12
  130. package/dist/meeting/muteState.js.map +1 -1
  131. package/dist/meeting/request.js +101 -104
  132. package/dist/meeting/request.js.map +1 -1
  133. package/dist/meeting/request.type.js.map +1 -1
  134. package/dist/meeting/state.js.map +1 -1
  135. package/dist/meeting/type.js.map +1 -1
  136. package/dist/meeting/util.js +118 -25
  137. package/dist/meeting/util.js.map +1 -1
  138. package/dist/meeting/voicea-meeting.js +3 -3
  139. package/dist/meeting/voicea-meeting.js.map +1 -1
  140. package/dist/meeting-info/collection.js +7 -10
  141. package/dist/meeting-info/collection.js.map +1 -1
  142. package/dist/meeting-info/index.js +1 -2
  143. package/dist/meeting-info/index.js.map +1 -1
  144. package/dist/meeting-info/meeting-info-v2.js +135 -146
  145. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  146. package/dist/meeting-info/request.js +1 -2
  147. package/dist/meeting-info/request.js.map +1 -1
  148. package/dist/meeting-info/util.js +36 -37
  149. package/dist/meeting-info/util.js.map +1 -1
  150. package/dist/meeting-info/utilv2.js +30 -31
  151. package/dist/meeting-info/utilv2.js.map +1 -1
  152. package/dist/meetings/collection.js +6 -8
  153. package/dist/meetings/collection.js.map +1 -1
  154. package/dist/meetings/index.js +276 -174
  155. package/dist/meetings/index.js.map +1 -1
  156. package/dist/meetings/meetings.types.js.map +1 -1
  157. package/dist/meetings/request.js +6 -8
  158. package/dist/meetings/request.js.map +1 -1
  159. package/dist/meetings/util.js +36 -30
  160. package/dist/meetings/util.js.map +1 -1
  161. package/dist/member/index.js +1 -2
  162. package/dist/member/index.js.map +1 -1
  163. package/dist/member/types.js +6 -3
  164. package/dist/member/types.js.map +1 -1
  165. package/dist/member/util.js.map +1 -1
  166. package/dist/members/collection.js +1 -2
  167. package/dist/members/collection.js.map +1 -1
  168. package/dist/members/index.js +18 -21
  169. package/dist/members/index.js.map +1 -1
  170. package/dist/members/request.js +8 -11
  171. package/dist/members/request.js.map +1 -1
  172. package/dist/members/types.js.map +1 -1
  173. package/dist/members/util.js.map +1 -1
  174. package/dist/metrics/constants.js +5 -1
  175. package/dist/metrics/constants.js.map +1 -1
  176. package/dist/metrics/index.js +3 -4
  177. package/dist/metrics/index.js.map +1 -1
  178. package/dist/multistream/mediaRequestManager.js +1 -2
  179. package/dist/multistream/mediaRequestManager.js.map +1 -1
  180. package/dist/multistream/receiveSlot.js +34 -45
  181. package/dist/multistream/receiveSlot.js.map +1 -1
  182. package/dist/multistream/receiveSlotManager.js +8 -9
  183. package/dist/multistream/receiveSlotManager.js.map +1 -1
  184. package/dist/multistream/remoteMedia.js +12 -15
  185. package/dist/multistream/remoteMedia.js.map +1 -1
  186. package/dist/multistream/remoteMediaGroup.js +1 -2
  187. package/dist/multistream/remoteMediaGroup.js.map +1 -1
  188. package/dist/multistream/remoteMediaManager.js +122 -123
  189. package/dist/multistream/remoteMediaManager.js.map +1 -1
  190. package/dist/multistream/sendSlotManager.js +29 -30
  191. package/dist/multistream/sendSlotManager.js.map +1 -1
  192. package/dist/personal-meeting-room/index.js +16 -19
  193. package/dist/personal-meeting-room/index.js.map +1 -1
  194. package/dist/personal-meeting-room/request.js +7 -10
  195. package/dist/personal-meeting-room/request.js.map +1 -1
  196. package/dist/personal-meeting-room/util.js.map +1 -1
  197. package/dist/reachability/clusterReachability.js +188 -352
  198. package/dist/reachability/clusterReachability.js.map +1 -1
  199. package/dist/reachability/index.js +212 -204
  200. package/dist/reachability/index.js.map +1 -1
  201. package/dist/reachability/reachability.types.js +14 -1
  202. package/dist/reachability/reachability.types.js.map +1 -1
  203. package/dist/reachability/reachabilityPeerConnection.js +445 -0
  204. package/dist/reachability/reachabilityPeerConnection.js.map +1 -0
  205. package/dist/reachability/request.js.map +1 -1
  206. package/dist/reachability/util.js.map +1 -1
  207. package/dist/reactions/constants.js.map +1 -1
  208. package/dist/reactions/reactions.js.map +1 -1
  209. package/dist/reactions/reactions.type.js.map +1 -1
  210. package/dist/reconnection-manager/index.js +178 -176
  211. package/dist/reconnection-manager/index.js.map +1 -1
  212. package/dist/recording-controller/enums.js.map +1 -1
  213. package/dist/recording-controller/index.js +1 -2
  214. package/dist/recording-controller/index.js.map +1 -1
  215. package/dist/recording-controller/util.js.map +1 -1
  216. package/dist/roap/index.js +12 -15
  217. package/dist/roap/index.js.map +1 -1
  218. package/dist/roap/request.js +24 -26
  219. package/dist/roap/request.js.map +1 -1
  220. package/dist/roap/turnDiscovery.js +75 -76
  221. package/dist/roap/turnDiscovery.js.map +1 -1
  222. package/dist/roap/types.js.map +1 -1
  223. package/dist/transcription/index.js +4 -5
  224. package/dist/transcription/index.js.map +1 -1
  225. package/dist/types/common/errors/webex-errors.d.ts +12 -0
  226. package/dist/types/config.d.ts +1 -0
  227. package/dist/types/constants.d.ts +28 -21
  228. package/dist/types/hashTree/constants.d.ts +8 -0
  229. package/dist/types/hashTree/hashTree.d.ts +129 -0
  230. package/dist/types/hashTree/hashTreeParser.d.ts +250 -0
  231. package/dist/types/hashTree/types.d.ts +33 -0
  232. package/dist/types/hashTree/utils.d.ts +16 -0
  233. package/dist/types/index.d.ts +2 -1
  234. package/dist/types/interceptors/locusRouteToken.d.ts +2 -0
  235. package/dist/types/locus-info/index.d.ts +98 -80
  236. package/dist/types/locus-info/types.d.ts +54 -0
  237. package/dist/types/media/MediaConnectionAwaiter.d.ts +10 -1
  238. package/dist/types/media/properties.d.ts +2 -1
  239. package/dist/types/meeting/index.d.ts +40 -12
  240. package/dist/types/meeting/util.d.ts +25 -0
  241. package/dist/types/meetings/index.d.ts +12 -3
  242. package/dist/types/metrics/constants.d.ts +4 -0
  243. package/dist/types/reachability/clusterReachability.d.ts +33 -84
  244. package/dist/types/reachability/reachability.types.d.ts +12 -1
  245. package/dist/types/reachability/reachabilityPeerConnection.d.ts +111 -0
  246. package/dist/types/reactions/reactions.type.d.ts +1 -0
  247. package/dist/types/webinar/utils.d.ts +6 -0
  248. package/dist/webinar/collection.js +1 -2
  249. package/dist/webinar/collection.js.map +1 -1
  250. package/dist/webinar/index.js +206 -158
  251. package/dist/webinar/index.js.map +1 -1
  252. package/dist/webinar/utils.js +25 -0
  253. package/dist/webinar/utils.js.map +1 -0
  254. package/package.json +24 -23
  255. package/src/common/errors/webex-errors.ts +19 -0
  256. package/src/config.ts +1 -0
  257. package/src/constants.ts +17 -2
  258. package/src/hashTree/constants.ts +9 -0
  259. package/src/hashTree/hashTree.ts +463 -0
  260. package/src/hashTree/hashTreeParser.ts +1143 -0
  261. package/src/hashTree/types.ts +39 -0
  262. package/src/hashTree/utils.ts +53 -0
  263. package/src/index.ts +2 -0
  264. package/src/interceptors/locusRouteToken.ts +22 -5
  265. package/src/locus-info/controlsUtils.ts +6 -0
  266. package/src/locus-info/index.ts +641 -164
  267. package/src/locus-info/types.ts +53 -0
  268. package/src/media/MediaConnectionAwaiter.ts +41 -1
  269. package/src/media/index.ts +6 -0
  270. package/src/media/properties.ts +3 -1
  271. package/src/meeting/index.ts +173 -37
  272. package/src/meeting/util.ts +119 -1
  273. package/src/meetings/index.ts +212 -67
  274. package/src/meetings/util.ts +10 -9
  275. package/src/metrics/constants.ts +4 -0
  276. package/src/reachability/clusterReachability.ts +159 -330
  277. package/src/reachability/index.ts +15 -1
  278. package/src/reachability/reachability.types.ts +15 -1
  279. package/src/reachability/reachabilityPeerConnection.ts +418 -0
  280. package/src/reactions/reactions.type.ts +1 -0
  281. package/src/webinar/index.ts +44 -1
  282. package/src/webinar/utils.ts +16 -0
  283. package/test/unit/spec/hashTree/hashTree.ts +655 -0
  284. package/test/unit/spec/hashTree/hashTreeParser.ts +1524 -0
  285. package/test/unit/spec/hashTree/utils.ts +140 -0
  286. package/test/unit/spec/interceptors/locusRouteToken.ts +44 -0
  287. package/test/unit/spec/locus-info/controlsUtils.js +27 -1
  288. package/test/unit/spec/locus-info/index.js +879 -16
  289. package/test/unit/spec/media/MediaConnectionAwaiter.ts +41 -1
  290. package/test/unit/spec/media/index.ts +140 -9
  291. package/test/unit/spec/media/properties.ts +12 -3
  292. package/test/unit/spec/meeting/index.js +514 -130
  293. package/test/unit/spec/meeting/utils.js +341 -15
  294. package/test/unit/spec/meetings/index.js +822 -32
  295. package/test/unit/spec/meetings/utils.js +51 -1
  296. package/test/unit/spec/reachability/clusterReachability.ts +404 -137
  297. package/test/unit/spec/reachability/index.ts +26 -3
  298. package/test/unit/spec/webinar/index.ts +106 -0
  299. package/test/unit/spec/webinar/utils.ts +39 -0
@@ -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';
@@ -31,6 +32,7 @@ const MeetingUtil = {
31
32
 
32
33
  // First todo: add check for existance
33
34
  parsed.locus = response.body.locus;
35
+ parsed.dataSets = response.body.dataSets;
34
36
  parsed.mediaConnections = response.body.mediaConnections;
35
37
  parsed.locusUrl = parsed.locus.url;
36
38
  parsed.locusId = parsed.locus.url.split('/').pop();
@@ -46,6 +48,111 @@ const MeetingUtil = {
46
48
  return parsed;
47
49
  },
48
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
+
49
156
  remoteUpdateAudioVideo: (meeting, audioMuted?: boolean, videoMuted?: boolean) => {
50
157
  if (!meeting) {
51
158
  return Promise.reject(new ParameterError('You need a meeting object.'));
@@ -202,6 +309,7 @@ const MeetingUtil = {
202
309
  const parsed = MeetingUtil.parseLocusJoin(res);
203
310
  meeting.setLocus(parsed);
204
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);
205
313
  webex.internal.newMetrics.submitClientEvent({
206
314
  name: 'client.locus.join.response',
207
315
  payload: {
@@ -209,6 +317,9 @@ const MeetingUtil = {
209
317
  identifiers: {
210
318
  trackingId: res.headers.trackingid,
211
319
  },
320
+ eventData: {
321
+ ...socketUrlInfo,
322
+ },
212
323
  },
213
324
  options: {
214
325
  meetingId: meeting.id,
@@ -219,12 +330,19 @@ const MeetingUtil = {
219
330
  return parsed;
220
331
  })
221
332
  .catch((err) => {
333
+ const socketUrlInfo = MeetingUtil.getSocketUrlInfo(webex);
222
334
  webex.internal.newMetrics.submitClientEvent({
223
335
  name: 'client.locus.join.response',
224
336
  payload: {
225
337
  identifiers: {meetingLookupUrl: meeting.meetingInfo?.meetingLookupUrl},
338
+ eventData: {
339
+ ...socketUrlInfo,
340
+ },
341
+ },
342
+ options: {
343
+ meetingId: meeting.id,
344
+ rawError: err,
226
345
  },
227
- options: {meetingId: meeting.id, rawError: err},
228
346
  });
229
347
 
230
348
  throw err;
@@ -1,5 +1,5 @@
1
1
  /* eslint no-shadow: ["error", { "allow": ["eventType"] }] */
2
- import {cloneDeep, clone} from 'lodash';
2
+ import {cloneDeep, clone, set} from 'lodash';
3
3
  import '@webex/internal-plugin-mercury';
4
4
  import '@webex/internal-plugin-conversation';
5
5
  import '@webex/internal-plugin-metrics';
@@ -66,6 +66,9 @@ import JoinWebinarError from '../common/errors/join-webinar-error';
66
66
  import {SpaceIDDeprecatedError} from '../common/errors/webex-errors';
67
67
  import NoMeetingInfoError from '../common/errors/no-meeting-info';
68
68
  import JoinForbiddenError from '../common/errors/join-forbidden-error';
69
+ import {HashTreeMessage} from '../hashTree/hashTreeParser';
70
+ import {HashTreeObject} from '../hashTree/types';
71
+ import {isSelf} from '../hashTree/utils';
69
72
 
70
73
  let mediaLogger;
71
74
 
@@ -94,6 +97,18 @@ class MediaLogger {
94
97
  LoggerProxy.logger.debug(...args);
95
98
  }
96
99
  }
100
+
101
+ export type LocusEvent = {
102
+ eventType: LOCUSEVENT;
103
+
104
+ // fields populated for "classic" locus events (eventType = 'locus.difference' and others, see LOCUSEVENT)
105
+ locusUrl?: string;
106
+ locus?: any;
107
+
108
+ // fields populated for "hash tree" locus events (eventType = 'locus.state_message' - see LOCUSEVENT.HASH_TREE_DATA_UPDATED)
109
+ stateElementsMessage?: HashTreeMessage;
110
+ };
111
+
97
112
  /**
98
113
  * Meetings Ready Event
99
114
  * Emitted when the meetings instance on webex is ready
@@ -180,6 +195,8 @@ export default class Meetings extends WebexPlugin {
180
195
  preferredWebexSite: any;
181
196
  reachability: Reachability;
182
197
  registered: any;
198
+ registrationPromise: Promise<void>;
199
+ unregistrationPromise: Promise<void>;
183
200
  request: any;
184
201
  geoHintInfo: any;
185
202
  meetingInfo: any;
@@ -406,29 +423,42 @@ export default class Meetings extends WebexPlugin {
406
423
  * @private
407
424
  * @memberof Meetings
408
425
  */
409
- getCorrespondingMeetingByLocus(data) {
410
- // getting meeting by correlationId. This will happen for the new event
411
- // Either the locus
412
- // TODO : Add check for the callBack Address
426
+ getCorrespondingMeetingByLocus(data: LocusEvent) {
427
+ const locusUrl =
428
+ data.stateElementsMessage?.locusUrl || // hash tree event
429
+ data.locusUrl; // classic event
430
+
431
+ // first try to find by locusUrl - that's the simplest and quickest way
432
+ const existingMeeting = this.meetingCollection.getByKey(MEETING_KEY.LOCUS_URL, locusUrl);
433
+
434
+ if (existingMeeting) {
435
+ return existingMeeting;
436
+ }
437
+
438
+ // if that didn't work, fallback to other fields like correlationId, sipUri, etc
439
+
440
+ // If the event is a hash tree event, we need to extract "self" object from it
441
+ // We don't care about the version, just need to find the meeting this event is for,
442
+ // so any hash tree object of type "self" will do
443
+ const hashTreeEventSelf = data.stateElementsMessage?.locusStateElements?.find(
444
+ (obj: HashTreeObject) => isSelf(obj)
445
+ )?.data;
446
+
447
+ const self = hashTreeEventSelf || data.locus?.self;
448
+
413
449
  return (
414
- this.meetingCollection.getByKey(MEETING_KEY.LOCUS_URL, data.locusUrl) ||
415
450
  // @ts-ignore
416
451
  this.meetingCollection.getByKey(
417
452
  MEETING_KEY.CORRELATION_ID,
418
453
  // @ts-ignore
419
- MeetingsUtil.checkForCorrelationId(this.webex.internal.device.url, data.locus)
420
- ) ||
421
- this.meetingCollection.getByKey(
422
- MEETING_KEY.SIP_URI,
423
- data.locus.self &&
424
- data.locus.self.callbackInfo &&
425
- data.locus.self.callbackInfo.callbackAddress
454
+ MeetingsUtil.getCorrelationIdForDevice(this.webex.internal.device.url, self)
426
455
  ) ||
427
- (data.locus.info?.isUnifiedSpaceMeeting
456
+ this.meetingCollection.getByKey(MEETING_KEY.SIP_URI, self?.callbackInfo?.callbackAddress) ||
457
+ (data.locus?.info?.isUnifiedSpaceMeeting
428
458
  ? undefined
429
459
  : this.meetingCollection.getByKey(
430
460
  MEETING_KEY.CONVERSATION_URL,
431
- data.locus.conversationUrl
461
+ data.locus?.conversationUrl
432
462
  )) ||
433
463
  this.meetingCollection.getByKey(MEETING_KEY.MEETINGNUMBER, data.locus?.info?.webExMeetingId)
434
464
  );
@@ -445,30 +475,33 @@ export default class Meetings extends WebexPlugin {
445
475
  * @private
446
476
  * @memberof Meetings
447
477
  */
448
- private handleLocusEvent(data: {locusUrl: string; locus: any}, useRandomDelayForInfo = false) {
478
+ private handleLocusEvent(data: LocusEvent, useRandomDelayForInfo = false) {
449
479
  let meeting = this.getCorrespondingMeetingByLocus(data);
450
480
 
451
481
  // Special case when locus has got replaced, This only happend once if a replace locus exists
452
482
  // https://sqbu-github.cisco.com/WebExSquared/locus/wiki/Locus-changing-mid-call
453
483
 
454
- if (!meeting && data.locus?.replaces?.length > 0) {
455
- // Always the last element in the replace is the active one
456
- meeting = this.meetingCollection.getByKey(
457
- MEETING_KEY.LOCUS_URL,
458
- data.locus.replaces[data.locus.replaces.length - 1].locusUrl
459
- );
460
- }
484
+ if (data.eventType !== LOCUSEVENT.HASH_TREE_DATA_UPDATED) {
485
+ if (!meeting && data.locus?.replaces?.length > 0) {
486
+ // Always the last element in the replace is the active one
487
+ meeting = this.meetingCollection.getByKey(
488
+ MEETING_KEY.LOCUS_URL,
489
+ data.locus.replaces[data.locus.replaces.length - 1].locusUrl
490
+ );
491
+ }
461
492
 
462
- if (meeting && !MeetingsUtil.isBreakoutLocusDTO(data.locus)) {
463
- meeting.locusInfo.updateMainSessionLocusCache(data.locus);
464
- }
465
- if (!this.isNeedHandleLocusDTO(meeting, data.locus)) {
466
- LoggerProxy.logger.log(
467
- `Meetings:index#handleLocusEvent --> doesn't need to process locus event`
468
- );
493
+ if (meeting && !MeetingsUtil.isBreakoutLocusDTO(data.locus)) {
494
+ meeting.locusInfo.updateMainSessionLocusCache(data.locus);
495
+ }
496
+ if (!this.isNeedHandleLocusDTO(meeting, data.locus)) {
497
+ LoggerProxy.logger.log(
498
+ `Meetings:index#handleLocusEvent --> doesn't need to process locus event`
499
+ );
469
500
 
470
- return;
501
+ return;
502
+ }
471
503
  }
504
+
472
505
  if (!meeting) {
473
506
  // TODO: create meeting when we get a meeting object
474
507
  // const checkForEnded = (locus) => {
@@ -489,42 +522,65 @@ export default class Meetings extends WebexPlugin {
489
522
  // };
490
523
  // rather then locus object change to locus url
491
524
 
492
- if (
493
- data.locus &&
494
- data.locus.fullState &&
495
- data.locus.fullState.state === LOCUS.STATE.INACTIVE
496
- ) {
497
- // just ignore the event as its already ended and not active
498
- LoggerProxy.logger.warn(
499
- 'Meetings:index#handleLocusEvent --> Locus event received for meeting, after it was ended.'
500
- );
525
+ if (data.eventType !== LOCUSEVENT.HASH_TREE_DATA_UPDATED) {
526
+ if (
527
+ data.locus &&
528
+ data.locus.fullState &&
529
+ data.locus.fullState.state === LOCUS.STATE.INACTIVE
530
+ ) {
531
+ // just ignore the event as its already ended and not active
532
+ LoggerProxy.logger.warn(
533
+ 'Meetings:index#handleLocusEvent --> Locus event received for meeting, after it was ended.'
534
+ );
501
535
 
502
- return;
503
- }
536
+ return;
537
+ }
504
538
 
505
- // When its wireless share or guest and user leaves the meeting we dont have to keep the meeting object
506
- // Any future events will be neglected
539
+ // When its wireless share or guest and user leaves the meeting we dont have to keep the meeting object
540
+ // Any future events will be neglected
541
+
542
+ if (
543
+ data.locus &&
544
+ data.locus.self &&
545
+ data.locus.self.state === _LEFT_ &&
546
+ data.locus.self.removed === true
547
+ ) {
548
+ // just ignore the event as its already ended and not active
549
+ LoggerProxy.logger.warn(
550
+ 'Meetings:index#handleLocusEvent --> Locus event received for meeting, after it was ended.'
551
+ );
507
552
 
508
- if (
509
- data.locus &&
510
- data.locus.self &&
511
- data.locus.self.state === _LEFT_ &&
512
- data.locus.self.removed === true
513
- ) {
514
- // just ignore the event as its already ended and not active
515
- LoggerProxy.logger.warn(
516
- 'Meetings:index#handleLocusEvent --> Locus event received for meeting, after it was ended.'
517
- );
553
+ return;
554
+ }
555
+ }
518
556
 
519
- return;
557
+ if (data.eventType === LOCUSEVENT.HASH_TREE_DATA_UPDATED) {
558
+ // in hash tree messages we don't ge the locus object, but the meeting constructor needs at least locus.url
559
+ set(data, 'locus.url', data.stateElementsMessage.locusUrl);
520
560
  }
521
561
 
522
562
  this.create(data.locus, DESTINATION_TYPE.LOCUS_ID, useRandomDelayForInfo)
523
- .then((newMeeting) => {
563
+ .then(async (newMeeting) => {
524
564
  meeting = newMeeting;
525
565
 
526
- // It's a new meeting so initialize the locus data
527
- meeting.locusInfo.initialSetup(data.locus);
566
+ try {
567
+ // It's a new meeting so initialize the locus data
568
+ await meeting.locusInfo.initialSetup({
569
+ trigger:
570
+ data.eventType === LOCUSEVENT.SDK_LOCUS_FROM_SYNC_MEETINGS
571
+ ? 'get-loci-response'
572
+ : 'locus-message',
573
+ locus: data.locus,
574
+ hashTreeMessage: data.stateElementsMessage,
575
+ });
576
+ } catch (error) {
577
+ LoggerProxy.logger.warn(
578
+ `Meetings:index#handleLocusEvent --> Error initializing locus data: ${error.message}`
579
+ );
580
+ // @ts-ignore
581
+ this.destroy(meeting, MEETING_REMOVED_REASON.LOCUS_DTO_SYNC_FAILED);
582
+ }
583
+
528
584
  this.checkHandleBreakoutLocus(data.locus);
529
585
  })
530
586
  .catch((e) => {
@@ -875,9 +931,20 @@ export default class Meetings extends WebexPlugin {
875
931
  * @returns {Promise} A promise that resolves when the step is completed.
876
932
  */
877
933
  executeRegistrationStep(step: () => Promise<any>, stepName: string) {
878
- return step().then(() => {
879
- this.registrationStatus[stepName] = true;
880
- });
934
+ return step()
935
+ .then(() => {
936
+ LoggerProxy.logger.info(
937
+ `Meetings:index#executeRegistrationStep --> INFO, ${stepName} completed`
938
+ );
939
+ this.registrationStatus[stepName] = true;
940
+ })
941
+ .catch((error) => {
942
+ LoggerProxy.logger.error(
943
+ `Meetings:index#executeRegistrationStep --> ERROR, ${stepName} failed: ${error.message}`
944
+ );
945
+
946
+ return Promise.reject(error);
947
+ });
881
948
  }
882
949
 
883
950
  /**
@@ -890,7 +957,33 @@ export default class Meetings extends WebexPlugin {
890
957
  * @memberof Meetings
891
958
  */
892
959
  public register(deviceRegistrationOptions?: DeviceRegistrationOptions): Promise<any> {
893
- this.registrationStatus = clone(INITIAL_REGISTRATION_STATUS);
960
+ if (this.unregistrationPromise) {
961
+ LoggerProxy.logger.info(
962
+ 'Meetings:index#register --> INFO, Meetings plugin unregistration in progress, waiting to register'
963
+ );
964
+
965
+ this.registrationPromise = this.unregistrationPromise
966
+ .catch(() => {}) // It doesn't matter what happened during unregistration
967
+ .finally(() => {
968
+ LoggerProxy.logger.info(
969
+ 'Meetings:index#register --> INFO, Meetings plugin unregistration completed, proceeding to register'
970
+ );
971
+
972
+ this.registrationPromise = null;
973
+
974
+ return this.register(deviceRegistrationOptions);
975
+ });
976
+
977
+ return this.registrationPromise;
978
+ }
979
+
980
+ if (this.registrationPromise) {
981
+ LoggerProxy.logger.info(
982
+ 'Meetings:index#register --> INFO, Meetings plugin registration in progress, returning existing promise'
983
+ );
984
+
985
+ return this.registrationPromise;
986
+ }
894
987
 
895
988
  // @ts-ignore
896
989
  if (!this.webex.canAuthorize) {
@@ -909,7 +1002,11 @@ export default class Meetings extends WebexPlugin {
909
1002
  return Promise.resolve();
910
1003
  }
911
1004
 
912
- return Promise.all([
1005
+ LoggerProxy.logger.info('Meetings:index#register --> INFO, Registering Meetings plugin');
1006
+
1007
+ this.registrationStatus = clone(INITIAL_REGISTRATION_STATUS);
1008
+
1009
+ this.registrationPromise = Promise.all([
913
1010
  this.executeRegistrationStep(() => this.fetchUserPreferredWebexSite(), 'fetchWebexSite'),
914
1011
  this.executeRegistrationStep(() => this.getGeoHint(), 'getGeoHint'),
915
1012
  this.executeRegistrationStep(
@@ -968,7 +1065,12 @@ export default class Meetings extends WebexPlugin {
968
1065
  });
969
1066
 
970
1067
  return Promise.reject(error);
1068
+ })
1069
+ .finally(() => {
1070
+ this.registrationPromise = null;
971
1071
  });
1072
+
1073
+ return this.registrationPromise;
972
1074
  }
973
1075
 
974
1076
  /**
@@ -980,6 +1082,35 @@ export default class Meetings extends WebexPlugin {
980
1082
  * @memberof Meetings
981
1083
  */
982
1084
  unregister() {
1085
+ if (this.unregistrationPromise) {
1086
+ LoggerProxy.logger.info(
1087
+ 'Meetings:index#unregister --> INFO, Meetings plugin unregistration in progress, returning existing promise'
1088
+ );
1089
+
1090
+ return this.unregistrationPromise;
1091
+ }
1092
+
1093
+ if (this.registrationPromise) {
1094
+ LoggerProxy.logger.info(
1095
+ 'Meetings:index#unregister --> INFO, Meetings plugin registration in progress, waiting to unregister'
1096
+ );
1097
+
1098
+ // Wait for registration to complete (success or failure), then call unregister again
1099
+ this.unregistrationPromise = this.registrationPromise
1100
+ .catch(() => {}) // It doesn't matter what happened during registration
1101
+ .finally(() => {
1102
+ LoggerProxy.logger.info(
1103
+ 'Meetings:index#unregister --> INFO, Meetings plugin registration completed, proceeding to unregister'
1104
+ );
1105
+
1106
+ this.unregistrationPromise = null;
1107
+
1108
+ return this.unregister();
1109
+ });
1110
+
1111
+ return this.unregistrationPromise;
1112
+ }
1113
+
983
1114
  if (!this.registered) {
984
1115
  LoggerProxy.logger.info(
985
1116
  'Meetings:index#unregister --> INFO, Meetings plugin already unregistered'
@@ -990,10 +1121,14 @@ export default class Meetings extends WebexPlugin {
990
1121
 
991
1122
  this.stopListeningForEvents();
992
1123
 
993
- return (
1124
+ this.unregistrationPromise =
994
1125
  // @ts-ignore
995
1126
  this.webex.internal.mercury
996
- .disconnect()
1127
+ // Use code 3050 with a non-reconnecting reason to prevent Mercury auto-reconnect
1128
+ // during unregister. Without this, disconnect() defaults to code 1000/"Done" which
1129
+ // force-closes as "Done (forced)" - a normalReconnectReason that triggers auto-reconnect,
1130
+ // causing a race condition with device.unregister().
1131
+ .disconnect({code: 3050, reason: 'meetings unregister'})
997
1132
  // @ts-ignore
998
1133
  .then(() => this.webex.internal.device.unregister())
999
1134
  .catch((error) => {
@@ -1023,7 +1158,11 @@ export default class Meetings extends WebexPlugin {
1023
1158
  this.registered = false;
1024
1159
  this.registrationStatus = clone(INITIAL_REGISTRATION_STATUS);
1025
1160
  })
1026
- );
1161
+ .finally(() => {
1162
+ this.unregistrationPromise = null;
1163
+ });
1164
+
1165
+ return this.unregistrationPromise;
1027
1166
  }
1028
1167
 
1029
1168
  /**
@@ -1739,6 +1878,7 @@ export default class Meetings extends WebexPlugin {
1739
1878
  lociToUpdate.forEach((locus) => {
1740
1879
  activeLocusUrl.push(locus.url);
1741
1880
  this.handleLocusEvent({
1881
+ eventType: LOCUSEVENT.SDK_LOCUS_FROM_SYNC_MEETINGS,
1742
1882
  locus,
1743
1883
  locusUrl: locus.url,
1744
1884
  });
@@ -1786,6 +1926,7 @@ export default class Meetings extends WebexPlugin {
1786
1926
  (mainLocus) => mainLocus.controls?.breakout?.url === breakoutLocus.controls?.breakout?.url
1787
1927
  );
1788
1928
  const existCorrespondingMeeting = this.getCorrespondingMeetingByLocus({
1929
+ eventType: LOCUSEVENT.SDK_NO_EVENT,
1789
1930
  locus: breakoutLocus,
1790
1931
  locusUrl: breakoutLocus.url,
1791
1932
  });
@@ -1831,7 +1972,11 @@ export default class Meetings extends WebexPlugin {
1831
1972
  }
1832
1973
 
1833
1974
  const associateBreakoutLocus = this.breakoutLocusForHandleLater[existIndex];
1834
- this.handleLocusEvent({locus: associateBreakoutLocus, locusUrl: associateBreakoutLocus.url});
1975
+ this.handleLocusEvent({
1976
+ eventType: LOCUSEVENT.SDK_NO_EVENT,
1977
+ locus: associateBreakoutLocus,
1978
+ locusUrl: associateBreakoutLocus.url,
1979
+ });
1835
1980
  this.breakoutLocusForHandleLater.splice(existIndex, 1);
1836
1981
  }
1837
1982
 
@@ -117,15 +117,16 @@ MeetingsUtil.getMediaServerIp = (sdp) => {
117
117
  return mediaServerIp;
118
118
  };
119
119
 
120
- MeetingsUtil.checkForCorrelationId = (deviceUrl, locus) => {
121
- let devices = [];
122
-
123
- if (locus) {
124
- if (locus && locus.self && locus.self.devices) {
125
- devices = locus.self.devices;
126
- }
127
-
128
- const foundDevice = devices.find((device) => device.url === deviceUrl);
120
+ /**
121
+ * Finds correlationId of a device from locus self devices array
122
+ * that matches the given deviceUrl
123
+ * @param {string} deviceUrl
124
+ * @param {object} locusSelf
125
+ * @returns {string|false} correlationId or false if not found
126
+ */
127
+ MeetingsUtil.getCorrelationIdForDevice = (deviceUrl: string, locusSelf: any) => {
128
+ if (locusSelf?.devices) {
129
+ const foundDevice = locusSelf?.devices.find((device) => device.url === deviceUrl);
129
130
 
130
131
  if (foundDevice && foundDevice.correlationId) {
131
132
  return foundDevice.correlationId;
@@ -21,6 +21,7 @@ const BEHAVIORAL_METRICS = {
21
21
  GET_DISPLAY_MEDIA_FAILURE: 'js_sdk_get_display_media_failures',
22
22
  JOIN_WITH_MEDIA_FAILURE: 'js_sdk_join_with_media_failures',
23
23
  LLM_CONNECTION_AFTER_JOIN_FAILURE: 'js_sdk_llm_connection_after_join_failure',
24
+ LLM_HEALTHCHECK_FAILURE: 'js_sdk_llm_healthcheck_failure',
24
25
  RECEIVE_TRANSCRIPTION_AFTER_JOIN_FAILURE: 'js_sdk_receive_transcription_after_join_failure',
25
26
 
26
27
  DISCONNECT_DUE_TO_INACTIVITY: 'js_sdk_disconnect_due_to_inactivity',
@@ -87,6 +88,9 @@ const BEHAVIORAL_METRICS = {
87
88
  VERIFY_REGISTRATION_ID_ERROR: 'js_sdk_verify_registrationId_error',
88
89
  JOIN_FORBIDDEN_ERROR: 'js_sdk_join_forbidden_error',
89
90
  MEDIA_ISSUE_DETECTED: 'js_sdk_media_issue_detected',
91
+ LOCUS_CLASSIC_VS_HASH_TREE_MISMATCH: 'js_sdk_locus_classic_vs_hash_tree_mismatch',
92
+ LOCUS_HASH_TREE_UNSUPPORTED_OPERATION: 'js_sdk_locus_hash_tree_unsupported_operation',
93
+ MEDIA_STILL_NOT_CONNECTED: 'js_sdk_media_still_not_connected',
90
94
  };
91
95
 
92
96
  export {BEHAVIORAL_METRICS as default};