@webex/plugin-meetings 3.11.0 → 3.12.0

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 (170) hide show
  1. package/dist/aiEnableRequest/index.js +184 -0
  2. package/dist/aiEnableRequest/index.js.map +1 -0
  3. package/dist/aiEnableRequest/utils.js +36 -0
  4. package/dist/aiEnableRequest/utils.js.map +1 -0
  5. package/dist/annotation/index.js +14 -5
  6. package/dist/annotation/index.js.map +1 -1
  7. package/dist/breakouts/breakout.js +1 -1
  8. package/dist/breakouts/index.js +1 -1
  9. package/dist/config.js +5 -1
  10. package/dist/config.js.map +1 -1
  11. package/dist/constants.js +28 -6
  12. package/dist/constants.js.map +1 -1
  13. package/dist/hashTree/constants.js +3 -1
  14. package/dist/hashTree/constants.js.map +1 -1
  15. package/dist/hashTree/hashTree.js +18 -0
  16. package/dist/hashTree/hashTree.js.map +1 -1
  17. package/dist/hashTree/hashTreeParser.js +709 -380
  18. package/dist/hashTree/hashTreeParser.js.map +1 -1
  19. package/dist/hashTree/types.js +4 -2
  20. package/dist/hashTree/types.js.map +1 -1
  21. package/dist/hashTree/utils.js +10 -0
  22. package/dist/hashTree/utils.js.map +1 -1
  23. package/dist/index.js +11 -2
  24. package/dist/index.js.map +1 -1
  25. package/dist/interceptors/constant.js +12 -0
  26. package/dist/interceptors/constant.js.map +1 -0
  27. package/dist/interceptors/dataChannelAuthToken.js +290 -0
  28. package/dist/interceptors/dataChannelAuthToken.js.map +1 -0
  29. package/dist/interceptors/index.js +7 -0
  30. package/dist/interceptors/index.js.map +1 -1
  31. package/dist/interceptors/utils.js +27 -0
  32. package/dist/interceptors/utils.js.map +1 -0
  33. package/dist/interpretation/index.js +2 -2
  34. package/dist/interpretation/index.js.map +1 -1
  35. package/dist/interpretation/siLanguage.js +1 -1
  36. package/dist/locus-info/controlsUtils.js +5 -3
  37. package/dist/locus-info/controlsUtils.js.map +1 -1
  38. package/dist/locus-info/index.js +217 -79
  39. package/dist/locus-info/index.js.map +1 -1
  40. package/dist/locus-info/selfUtils.js +1 -0
  41. package/dist/locus-info/selfUtils.js.map +1 -1
  42. package/dist/locus-info/types.js.map +1 -1
  43. package/dist/media/MediaConnectionAwaiter.js +57 -1
  44. package/dist/media/MediaConnectionAwaiter.js.map +1 -1
  45. package/dist/media/properties.js +4 -2
  46. package/dist/media/properties.js.map +1 -1
  47. package/dist/meeting/in-meeting-actions.js +7 -1
  48. package/dist/meeting/in-meeting-actions.js.map +1 -1
  49. package/dist/meeting/index.js +1082 -861
  50. package/dist/meeting/index.js.map +1 -1
  51. package/dist/meeting/request.js +50 -0
  52. package/dist/meeting/request.js.map +1 -1
  53. package/dist/meeting/request.type.js.map +1 -1
  54. package/dist/meeting/util.js +133 -3
  55. package/dist/meeting/util.js.map +1 -1
  56. package/dist/meetings/index.js +100 -45
  57. package/dist/meetings/index.js.map +1 -1
  58. package/dist/member/index.js +10 -0
  59. package/dist/member/index.js.map +1 -1
  60. package/dist/member/util.js +10 -0
  61. package/dist/member/util.js.map +1 -1
  62. package/dist/metrics/constants.js +2 -1
  63. package/dist/metrics/constants.js.map +1 -1
  64. package/dist/multistream/mediaRequestManager.js +9 -60
  65. package/dist/multistream/mediaRequestManager.js.map +1 -1
  66. package/dist/multistream/remoteMediaManager.js +11 -0
  67. package/dist/multistream/remoteMediaManager.js.map +1 -1
  68. package/dist/reachability/index.js +18 -10
  69. package/dist/reachability/index.js.map +1 -1
  70. package/dist/reactions/reactions.type.js.map +1 -1
  71. package/dist/reconnection-manager/index.js +0 -1
  72. package/dist/reconnection-manager/index.js.map +1 -1
  73. package/dist/types/aiEnableRequest/index.d.ts +5 -0
  74. package/dist/types/aiEnableRequest/utils.d.ts +2 -0
  75. package/dist/types/config.d.ts +3 -0
  76. package/dist/types/constants.d.ts +23 -1
  77. package/dist/types/hashTree/constants.d.ts +1 -0
  78. package/dist/types/hashTree/hashTree.d.ts +7 -0
  79. package/dist/types/hashTree/hashTreeParser.d.ts +99 -14
  80. package/dist/types/hashTree/types.d.ts +3 -0
  81. package/dist/types/hashTree/utils.d.ts +6 -0
  82. package/dist/types/index.d.ts +1 -0
  83. package/dist/types/interceptors/constant.d.ts +5 -0
  84. package/dist/types/interceptors/dataChannelAuthToken.d.ts +43 -0
  85. package/dist/types/interceptors/index.d.ts +2 -1
  86. package/dist/types/interceptors/utils.d.ts +1 -0
  87. package/dist/types/locus-info/index.d.ts +21 -2
  88. package/dist/types/locus-info/types.d.ts +1 -0
  89. package/dist/types/media/MediaConnectionAwaiter.d.ts +10 -1
  90. package/dist/types/media/properties.d.ts +2 -1
  91. package/dist/types/meeting/in-meeting-actions.d.ts +6 -0
  92. package/dist/types/meeting/index.d.ts +38 -6
  93. package/dist/types/meeting/request.d.ts +16 -1
  94. package/dist/types/meeting/request.type.d.ts +5 -0
  95. package/dist/types/meeting/util.d.ts +31 -0
  96. package/dist/types/meetings/index.d.ts +4 -2
  97. package/dist/types/member/index.d.ts +1 -0
  98. package/dist/types/member/util.d.ts +5 -0
  99. package/dist/types/metrics/constants.d.ts +1 -0
  100. package/dist/types/multistream/mediaRequestManager.d.ts +0 -23
  101. package/dist/types/reactions/reactions.type.d.ts +1 -0
  102. package/dist/types/webinar/utils.d.ts +6 -0
  103. package/dist/webinar/index.js +260 -90
  104. package/dist/webinar/index.js.map +1 -1
  105. package/dist/webinar/utils.js +25 -0
  106. package/dist/webinar/utils.js.map +1 -0
  107. package/package.json +24 -23
  108. package/src/aiEnableRequest/README.md +84 -0
  109. package/src/aiEnableRequest/index.ts +170 -0
  110. package/src/aiEnableRequest/utils.ts +25 -0
  111. package/src/annotation/index.ts +27 -7
  112. package/src/config.ts +3 -0
  113. package/src/constants.ts +29 -1
  114. package/src/hashTree/constants.ts +1 -0
  115. package/src/hashTree/hashTree.ts +17 -0
  116. package/src/hashTree/hashTreeParser.ts +627 -249
  117. package/src/hashTree/types.ts +4 -0
  118. package/src/hashTree/utils.ts +9 -0
  119. package/src/index.ts +8 -1
  120. package/src/interceptors/constant.ts +6 -0
  121. package/src/interceptors/dataChannelAuthToken.ts +170 -0
  122. package/src/interceptors/index.ts +2 -1
  123. package/src/interceptors/utils.ts +16 -0
  124. package/src/interpretation/index.ts +2 -2
  125. package/src/locus-info/controlsUtils.ts +11 -0
  126. package/src/locus-info/index.ts +231 -61
  127. package/src/locus-info/selfUtils.ts +1 -0
  128. package/src/locus-info/types.ts +1 -0
  129. package/src/media/MediaConnectionAwaiter.ts +41 -1
  130. package/src/media/properties.ts +3 -1
  131. package/src/meeting/in-meeting-actions.ts +12 -0
  132. package/src/meeting/index.ts +205 -44
  133. package/src/meeting/request.ts +42 -0
  134. package/src/meeting/request.type.ts +6 -0
  135. package/src/meeting/util.ts +160 -2
  136. package/src/meetings/index.ts +135 -41
  137. package/src/member/index.ts +10 -0
  138. package/src/member/util.ts +12 -0
  139. package/src/metrics/constants.ts +1 -0
  140. package/src/multistream/mediaRequestManager.ts +4 -54
  141. package/src/multistream/remoteMediaManager.ts +13 -0
  142. package/src/reachability/index.ts +9 -0
  143. package/src/reactions/reactions.type.ts +1 -0
  144. package/src/reconnection-manager/index.ts +0 -1
  145. package/src/webinar/index.ts +162 -5
  146. package/src/webinar/utils.ts +16 -0
  147. package/test/unit/spec/aiEnableRequest/index.ts +981 -0
  148. package/test/unit/spec/aiEnableRequest/utils.ts +130 -0
  149. package/test/unit/spec/annotation/index.ts +69 -7
  150. package/test/unit/spec/hashTree/hashTree.ts +66 -0
  151. package/test/unit/spec/hashTree/hashTreeParser.ts +1869 -189
  152. package/test/unit/spec/interceptors/dataChannelAuthToken.ts +210 -0
  153. package/test/unit/spec/interceptors/utils.ts +75 -0
  154. package/test/unit/spec/locus-info/controlsUtils.js +29 -0
  155. package/test/unit/spec/locus-info/index.js +383 -46
  156. package/test/unit/spec/media/MediaConnectionAwaiter.ts +41 -1
  157. package/test/unit/spec/media/properties.ts +12 -3
  158. package/test/unit/spec/meeting/in-meeting-actions.ts +8 -2
  159. package/test/unit/spec/meeting/index.js +716 -115
  160. package/test/unit/spec/meeting/request.js +70 -0
  161. package/test/unit/spec/meeting/utils.js +438 -26
  162. package/test/unit/spec/meetings/index.js +652 -31
  163. package/test/unit/spec/member/index.js +28 -4
  164. package/test/unit/spec/member/util.js +65 -27
  165. package/test/unit/spec/multistream/mediaRequestManager.ts +2 -85
  166. package/test/unit/spec/multistream/remoteMediaManager.ts +30 -0
  167. package/test/unit/spec/reachability/index.ts +23 -0
  168. package/test/unit/spec/reconnection-manager/index.js +4 -8
  169. package/test/unit/spec/webinar/index.ts +348 -36
  170. package/test/unit/spec/webinar/utils.ts +39 -0
@@ -0,0 +1,210 @@
1
+ import 'jsdom-global/register';
2
+ import {assert, expect} from '@webex/test-helper-chai';
3
+ import sinon from 'sinon';
4
+ import MockWebex from '@webex/test-helper-mock-webex';
5
+ import {WebexHttpError} from '@webex/webex-core';
6
+ import DataChannelAuthTokenInterceptor from '@webex/plugin-meetings/src/interceptors/dataChannelAuthToken';
7
+ import LoggerProxy from '@webex/plugin-meetings/src/common/logs/logger-proxy';
8
+ import * as utils from '@webex/plugin-meetings/src/interceptors/utils';
9
+ import {DATA_CHANNEL_AUTH_HEADER, MAX_RETRY} from '@webex/plugin-meetings/src/interceptors/constant';
10
+
11
+ describe('plugin-meetings', () => {
12
+ describe('Interceptors', () => {
13
+ describe('DataChannelAuthTokenInterceptor', () => {
14
+ let interceptor, webex, clock;
15
+
16
+ beforeEach(() => {
17
+ clock = sinon.useFakeTimers();
18
+ sinon.stub(LoggerProxy, 'logger').value({
19
+ error: sinon.stub(),
20
+ warn: sinon.stub(),
21
+ });
22
+
23
+ webex = new MockWebex({children: {}});
24
+ webex.request = sinon.stub().resolves({});
25
+
26
+ interceptor = Reflect.apply(DataChannelAuthTokenInterceptor.create, webex, []);
27
+
28
+ interceptor._refreshDataChannelToken = sinon.stub();
29
+ interceptor._isDataChannelTokenEnabled = sinon.stub().resolves(true);
30
+ });
31
+
32
+ afterEach(() => {
33
+ sinon.restore();
34
+ clock.restore();
35
+ });
36
+
37
+ const makeReason = (statusCode) =>
38
+ new WebexHttpError({
39
+ statusCode,
40
+ options: {headers: {}, uri: 'https://example.com'},
41
+ body: {},
42
+ });
43
+
44
+ describe('#onResponseError', () => {
45
+ it('rejects when no Data-Channel-Auth-Token header exists', async () => {
46
+ const options = {headers: {}};
47
+ const reason = makeReason(401);
48
+
49
+ await assert.isRejected(interceptor.onResponseError(options, reason), reason);
50
+ });
51
+
52
+ it('rejects when statusCode is not 401/403', async () => {
53
+ const options = {headers: {[DATA_CHANNEL_AUTH_HEADER]: 'abc'}};
54
+ const reason = makeReason(500);
55
+
56
+ await assert.isRejected(interceptor.onResponseError(options, reason), reason);
57
+ });
58
+
59
+ it('rejects when retry count exceeds MAX_RETRY', async () => {
60
+ const options = {headers: {[DATA_CHANNEL_AUTH_HEADER]: 'abc'}};
61
+ const reason = makeReason(401);
62
+
63
+ for (let i = 0; i < MAX_RETRY; i++) {
64
+ interceptor.onResponseError(options, reason).catch(() => {});
65
+ }
66
+
67
+ await assert.isRejected(interceptor.onResponseError(options, reason), reason);
68
+
69
+ sinon.assert.calledOnce(LoggerProxy.logger.error);
70
+ });
71
+
72
+ it('calls refreshTokenAndRetryWithDelay when eligible', async () => {
73
+ const options = {headers: {[DATA_CHANNEL_AUTH_HEADER]: 'abc'}};
74
+ const reason = makeReason(401);
75
+
76
+ interceptor._isDataChannelTokenEnabled.resolves(true);
77
+
78
+ const stub = sinon.stub(interceptor, 'refreshTokenAndRetryWithDelay').resolves('ok');
79
+
80
+ await interceptor.onResponseError(options, reason);
81
+
82
+ sinon.assert.calledOnceWithExactly(stub, options);
83
+ });
84
+
85
+ it('rejects when isDataChannelTokenEnabled is false', async () => {
86
+ const options = {headers: {[DATA_CHANNEL_AUTH_HEADER]: 'abc'}};
87
+ const reason = makeReason(401);
88
+
89
+ interceptor._isDataChannelTokenEnabled.resolves(false);
90
+
91
+ await assert.isRejected(interceptor.onResponseError(options, reason), reason);
92
+ });
93
+ });
94
+
95
+ describe('#onRequest', () => {
96
+ let isJwtTokenExpiredStub;
97
+
98
+ beforeEach(() => {
99
+ isJwtTokenExpiredStub = sinon.stub(utils, 'isJwtTokenExpired').returns(false);
100
+ });
101
+
102
+ it('does nothing when token is missing', async () => {
103
+ const options = {headers: {}};
104
+
105
+ const res = await interceptor.onRequest(options);
106
+
107
+ expect(res).to.equal(options);
108
+ sinon.assert.notCalled(isJwtTokenExpiredStub);
109
+ });
110
+
111
+ it('does nothing when feature is disabled', async () => {
112
+ interceptor._isDataChannelTokenEnabled.resolves(false);
113
+
114
+ const options = {headers: {[DATA_CHANNEL_AUTH_HEADER]: 'old-token'}};
115
+ const res = await interceptor.onRequest(options);
116
+
117
+ expect(res).to.equal(options);
118
+ sinon.assert.notCalled(isJwtTokenExpiredStub);
119
+ });
120
+
121
+ it('does not refresh when token is not expired', async () => {
122
+ interceptor._isDataChannelTokenEnabled.resolves(true);
123
+ isJwtTokenExpiredStub.returns(false);
124
+
125
+ const options = {headers: {[DATA_CHANNEL_AUTH_HEADER]: 'old-token'}};
126
+ const res = await interceptor.onRequest(options);
127
+
128
+ sinon.assert.notCalled(interceptor._refreshDataChannelToken);
129
+ expect(res.headers[DATA_CHANNEL_AUTH_HEADER]).to.equal('old-token');
130
+ });
131
+
132
+ it('refreshes token when expired', async () => {
133
+ interceptor._isDataChannelTokenEnabled.resolves(true);
134
+ isJwtTokenExpiredStub.returns(true);
135
+
136
+ interceptor._refreshDataChannelToken.resolves('new-token');
137
+
138
+ const options = {headers: {[DATA_CHANNEL_AUTH_HEADER]: 'old-token'}};
139
+ const res = await interceptor.onRequest(options);
140
+
141
+ sinon.assert.calledOnce(interceptor._refreshDataChannelToken);
142
+ expect(res.headers[DATA_CHANNEL_AUTH_HEADER]).to.equal('new-token');
143
+ });
144
+
145
+ it('continues request when refresh fails', async () => {
146
+ interceptor._isDataChannelTokenEnabled.resolves(true);
147
+ isJwtTokenExpiredStub.returns(true);
148
+
149
+ interceptor._refreshDataChannelToken.rejects(new Error('refresh failed'));
150
+
151
+ const options = {headers: {[DATA_CHANNEL_AUTH_HEADER]: 'old-token'}};
152
+ const res = await interceptor.onRequest(options);
153
+
154
+ expect(res.headers[DATA_CHANNEL_AUTH_HEADER]).to.equal('old-token');
155
+ });
156
+ });
157
+
158
+ describe('#refreshTokenAndRetryWithDelay', () => {
159
+ const options = {
160
+ headers: {[DATA_CHANNEL_AUTH_HEADER]: 'old-token'},
161
+ method: 'GET',
162
+ uri: 'https://example.com',
163
+ };
164
+
165
+ it('refreshes token and retries request successfully', async () => {
166
+ interceptor._refreshDataChannelToken.resolves('new-token');
167
+ webex.request.resolves('mock-response');
168
+
169
+ const promise = interceptor.refreshTokenAndRetryWithDelay(options);
170
+
171
+ clock.tick(2000);
172
+
173
+ const result = await promise;
174
+
175
+ expect(interceptor._refreshDataChannelToken.calledOnce).to.be.true;
176
+ expect(options.headers[DATA_CHANNEL_AUTH_HEADER]).to.equal('new-token');
177
+ expect(webex.request.calledOnceWith(options)).to.be.true;
178
+ expect(result).to.equal('mock-response');
179
+ });
180
+
181
+ it('rejects when refreshDataChannelToken fails', async () => {
182
+ interceptor._refreshDataChannelToken.rejects(new Error('refresh failed'));
183
+
184
+ const promise = interceptor.refreshTokenAndRetryWithDelay(options);
185
+
186
+ clock.tick(2000);
187
+
188
+ await assert.isRejected(
189
+ promise,
190
+ /DataChannel token refresh failed: refresh failed/
191
+ );
192
+ });
193
+
194
+ it('rejects when retry request fails', async () => {
195
+ interceptor._refreshDataChannelToken.resolves('new-token');
196
+ webex.request.rejects(new Error('request failed'));
197
+
198
+ const promise = interceptor.refreshTokenAndRetryWithDelay(options);
199
+
200
+ clock.tick(2000);
201
+
202
+ await assert.isRejected(
203
+ promise,
204
+ /DataChannel token refresh failed: request failed/
205
+ );
206
+ });
207
+ });
208
+ });
209
+ });
210
+ });
@@ -0,0 +1,75 @@
1
+ import 'jsdom-global/register';
2
+ import {expect} from '@webex/test-helper-chai';
3
+ import sinon from 'sinon';
4
+ import {isJwtTokenExpired} from '@webex/plugin-meetings/src/interceptors/utils';
5
+
6
+ const makeJwt = (payload) =>
7
+ [
8
+ Buffer.from(JSON.stringify({alg: 'none', typ: 'JWT'})).toString('base64url'),
9
+ Buffer.from(JSON.stringify(payload)).toString('base64url'),
10
+ ''
11
+ ].join('.');
12
+
13
+ describe('plugin-meetings', () => {
14
+ describe('Interceptors', () => {
15
+ describe('utils - isJwtTokenExpired', () => {
16
+ let clock;
17
+
18
+ beforeEach(() => {
19
+ clock = sinon.useFakeTimers();
20
+ });
21
+
22
+ afterEach(() => {
23
+ sinon.restore();
24
+ clock.restore();
25
+ });
26
+
27
+ it('returns false when token has no exp', () => {
28
+ const token = makeJwt({}); // no exp
29
+
30
+ const result = isJwtTokenExpired(token);
31
+
32
+ expect(result).to.equal(false);
33
+ });
34
+
35
+ it('returns false when token is not expired', () => {
36
+ const now = Date.now();
37
+ const futureExp = Math.floor((now + 60 * 1000) / 1000);
38
+
39
+ const token = makeJwt({exp: futureExp});
40
+
41
+ const result = isJwtTokenExpired(token);
42
+
43
+ expect(result).to.equal(false);
44
+ });
45
+
46
+ it('returns true when token is expired', () => {
47
+ const now = Date.now();
48
+ const pastExp = Math.floor((now - 60 * 1000) / 1000);
49
+
50
+ const token = makeJwt({exp: pastExp});
51
+
52
+ const result = isJwtTokenExpired(token);
53
+
54
+ expect(result).to.equal(true);
55
+ });
56
+
57
+ it('returns true when token expires within EXPIRY_BUFFER', () => {
58
+ const now = Date.now();
59
+ const expSoon = Math.floor((now + 10 * 1000) / 1000);
60
+
61
+ const token = makeJwt({exp: expSoon});
62
+
63
+ const result = isJwtTokenExpired(token);
64
+
65
+ expect(result).to.equal(true);
66
+ });
67
+
68
+ it('returns true when token is invalid', () => {
69
+ const result = isJwtTokenExpired('not-a-jwt');
70
+
71
+ expect(result).to.equal(true);
72
+ });
73
+ });
74
+ });
75
+ });
@@ -489,6 +489,35 @@ describe('plugin-meetings', () => {
489
489
  assert.equal(updates.hasHesiodLLMIdChanged, true);
490
490
  });
491
491
 
492
+ describe('hasAiSummaryNotificationChanged', () => {
493
+ it('returns false when aiSummaryNotification has not changed', () => {
494
+ const previous = {transcribe: {aiSummaryNotification: false}};
495
+ const current = {transcribe: {aiSummaryNotification: false}};
496
+ const {updates} = ControlsUtils.getControls(previous, current);
497
+ assert.equal(updates.hasAiSummaryNotificationChanged, false);
498
+ });
499
+
500
+ it('returns true when aiSummaryNotification changes from false to true', () => {
501
+ const previous = {transcribe: {aiSummaryNotification: false}};
502
+ const current = {transcribe: {aiSummaryNotification: true}};
503
+ const {updates} = ControlsUtils.getControls(previous, current);
504
+ assert.equal(updates.hasAiSummaryNotificationChanged, true);
505
+ });
506
+
507
+ it('returns true when aiSummaryNotification changes from undefined to true', () => {
508
+ const previous = {transcribe: undefined};
509
+ const current = {transcribe: {aiSummaryNotification: true}};
510
+ const {updates} = ControlsUtils.getControls(previous, current);
511
+ assert.equal(updates.hasAiSummaryNotificationChanged, true);
512
+ });
513
+
514
+ it('parses aiSummaryNotification into the transcribe block', () => {
515
+ const controls = {transcribe: {transcribing: false, caption: false, aiSummaryNotification: true}};
516
+ const parsed = ControlsUtils.parse(controls);
517
+ assert.equal(parsed.transcribe.aiSummaryNotification, true);
518
+ });
519
+ });
520
+
492
521
  describe('videoEnabled', () => {
493
522
  const testVideoEnabled = (oldControls, newControls, updatedProperty) => {
494
523
  const result = ControlsUtils.getControls(oldControls, newControls);