@webex/plugin-meetings 3.12.0-next.5 → 3.12.0-next.50

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 (136) hide show
  1. package/AGENTS.md +9 -0
  2. package/dist/aiEnableRequest/index.js +15 -2
  3. package/dist/aiEnableRequest/index.js.map +1 -1
  4. package/dist/breakouts/breakout.js +6 -2
  5. package/dist/breakouts/breakout.js.map +1 -1
  6. package/dist/breakouts/index.js +1 -1
  7. package/dist/config.js +1 -0
  8. package/dist/config.js.map +1 -1
  9. package/dist/constants.js +6 -3
  10. package/dist/constants.js.map +1 -1
  11. package/dist/controls-options-manager/constants.js +11 -1
  12. package/dist/controls-options-manager/constants.js.map +1 -1
  13. package/dist/controls-options-manager/index.js +38 -24
  14. package/dist/controls-options-manager/index.js.map +1 -1
  15. package/dist/controls-options-manager/util.js +91 -0
  16. package/dist/controls-options-manager/util.js.map +1 -1
  17. package/dist/hashTree/constants.js +10 -1
  18. package/dist/hashTree/constants.js.map +1 -1
  19. package/dist/hashTree/hashTreeParser.js +593 -358
  20. package/dist/hashTree/hashTreeParser.js.map +1 -1
  21. package/dist/hashTree/utils.js +22 -0
  22. package/dist/hashTree/utils.js.map +1 -1
  23. package/dist/index.js +7 -0
  24. package/dist/index.js.map +1 -1
  25. package/dist/interceptors/locusRetry.js +23 -8
  26. package/dist/interceptors/locusRetry.js.map +1 -1
  27. package/dist/interpretation/index.js +10 -1
  28. package/dist/interpretation/index.js.map +1 -1
  29. package/dist/interpretation/siLanguage.js +1 -1
  30. package/dist/locus-info/controlsUtils.js +4 -1
  31. package/dist/locus-info/controlsUtils.js.map +1 -1
  32. package/dist/locus-info/index.js +277 -86
  33. package/dist/locus-info/index.js.map +1 -1
  34. package/dist/locus-info/types.js +16 -0
  35. package/dist/locus-info/types.js.map +1 -1
  36. package/dist/media/properties.js +1 -0
  37. package/dist/media/properties.js.map +1 -1
  38. package/dist/meeting/in-meeting-actions.js +3 -1
  39. package/dist/meeting/in-meeting-actions.js.map +1 -1
  40. package/dist/meeting/index.js +842 -521
  41. package/dist/meeting/index.js.map +1 -1
  42. package/dist/meeting/util.js +19 -2
  43. package/dist/meeting/util.js.map +1 -1
  44. package/dist/meetings/index.js +199 -77
  45. package/dist/meetings/index.js.map +1 -1
  46. package/dist/meetings/meetings.types.js +6 -1
  47. package/dist/meetings/meetings.types.js.map +1 -1
  48. package/dist/meetings/request.js +39 -0
  49. package/dist/meetings/request.js.map +1 -1
  50. package/dist/meetings/util.js +67 -5
  51. package/dist/meetings/util.js.map +1 -1
  52. package/dist/member/index.js +10 -0
  53. package/dist/member/index.js.map +1 -1
  54. package/dist/member/types.js.map +1 -1
  55. package/dist/member/util.js +3 -0
  56. package/dist/member/util.js.map +1 -1
  57. package/dist/metrics/constants.js +2 -1
  58. package/dist/metrics/constants.js.map +1 -1
  59. package/dist/recording-controller/index.js +1 -3
  60. package/dist/recording-controller/index.js.map +1 -1
  61. package/dist/types/config.d.ts +1 -0
  62. package/dist/types/constants.d.ts +2 -0
  63. package/dist/types/controls-options-manager/constants.d.ts +6 -1
  64. package/dist/types/controls-options-manager/index.d.ts +10 -0
  65. package/dist/types/hashTree/constants.d.ts +1 -0
  66. package/dist/types/hashTree/hashTreeParser.d.ts +61 -15
  67. package/dist/types/hashTree/utils.d.ts +11 -0
  68. package/dist/types/index.d.ts +2 -0
  69. package/dist/types/interceptors/locusRetry.d.ts +4 -4
  70. package/dist/types/locus-info/index.d.ts +46 -6
  71. package/dist/types/locus-info/types.d.ts +17 -1
  72. package/dist/types/media/properties.d.ts +1 -0
  73. package/dist/types/meeting/in-meeting-actions.d.ts +2 -0
  74. package/dist/types/meeting/index.d.ts +70 -1
  75. package/dist/types/meeting/util.d.ts +8 -0
  76. package/dist/types/meetings/index.d.ts +18 -1
  77. package/dist/types/meetings/meetings.types.d.ts +15 -0
  78. package/dist/types/meetings/request.d.ts +14 -0
  79. package/dist/types/member/index.d.ts +1 -0
  80. package/dist/types/member/types.d.ts +1 -0
  81. package/dist/types/member/util.d.ts +1 -0
  82. package/dist/types/metrics/constants.d.ts +1 -0
  83. package/dist/webinar/index.js +361 -235
  84. package/dist/webinar/index.js.map +1 -1
  85. package/package.json +22 -22
  86. package/src/aiEnableRequest/index.ts +16 -0
  87. package/src/breakouts/breakout.ts +2 -1
  88. package/src/config.ts +1 -0
  89. package/src/constants.ts +5 -1
  90. package/src/controls-options-manager/constants.ts +14 -1
  91. package/src/controls-options-manager/index.ts +47 -24
  92. package/src/controls-options-manager/util.ts +81 -1
  93. package/src/hashTree/constants.ts +9 -0
  94. package/src/hashTree/hashTreeParser.ts +306 -160
  95. package/src/hashTree/utils.ts +17 -0
  96. package/src/index.ts +5 -0
  97. package/src/interceptors/locusRetry.ts +25 -4
  98. package/src/interpretation/index.ts +25 -8
  99. package/src/locus-info/controlsUtils.ts +3 -1
  100. package/src/locus-info/index.ts +276 -93
  101. package/src/locus-info/types.ts +19 -1
  102. package/src/media/properties.ts +1 -0
  103. package/src/meeting/in-meeting-actions.ts +4 -0
  104. package/src/meeting/index.ts +315 -26
  105. package/src/meeting/util.ts +20 -2
  106. package/src/meetings/index.ts +104 -43
  107. package/src/meetings/meetings.types.ts +19 -0
  108. package/src/meetings/request.ts +43 -0
  109. package/src/meetings/util.ts +80 -1
  110. package/src/member/index.ts +10 -0
  111. package/src/member/types.ts +1 -0
  112. package/src/member/util.ts +3 -0
  113. package/src/metrics/constants.ts +1 -0
  114. package/src/recording-controller/index.ts +1 -2
  115. package/src/webinar/index.ts +162 -21
  116. package/test/unit/spec/aiEnableRequest/index.ts +86 -0
  117. package/test/unit/spec/breakouts/breakout.ts +7 -3
  118. package/test/unit/spec/controls-options-manager/index.js +140 -29
  119. package/test/unit/spec/controls-options-manager/util.js +165 -0
  120. package/test/unit/spec/hashTree/hashTreeParser.ts +1294 -191
  121. package/test/unit/spec/hashTree/utils.ts +88 -1
  122. package/test/unit/spec/interceptors/locusRetry.ts +205 -4
  123. package/test/unit/spec/interpretation/index.ts +26 -4
  124. package/test/unit/spec/locus-info/controlsUtils.js +172 -57
  125. package/test/unit/spec/locus-info/index.js +443 -81
  126. package/test/unit/spec/meeting/in-meeting-actions.ts +2 -0
  127. package/test/unit/spec/meeting/index.js +836 -41
  128. package/test/unit/spec/meeting/muteState.js +3 -0
  129. package/test/unit/spec/meeting/utils.js +33 -0
  130. package/test/unit/spec/meetings/index.js +275 -10
  131. package/test/unit/spec/meetings/request.js +141 -0
  132. package/test/unit/spec/meetings/utils.js +161 -0
  133. package/test/unit/spec/member/index.js +7 -0
  134. package/test/unit/spec/member/util.js +24 -0
  135. package/test/unit/spec/recording-controller/index.js +9 -8
  136. package/test/unit/spec/webinar/index.ts +141 -16
@@ -1,5 +1,10 @@
1
1
  import {HashTreeObject, ObjectType} from '../../../../src/hashTree/types';
2
- import {deleteNestedObjectsWithHtMeta, isSelf} from '../../../../src/hashTree/utils';
2
+ import {
3
+ deleteNestedObjectsWithHtMeta,
4
+ isSelf,
5
+ sortByInitPriority,
6
+ } from '../../../../src/hashTree/utils';
7
+ import {DataSetNames, DATA_SET_INIT_PRIORITY} from '../../../../src/hashTree/constants';
3
8
 
4
9
  import {assert} from '@webex/test-helper-chai';
5
10
 
@@ -137,4 +142,86 @@ describe('Hash Tree Utils', () => {
137
142
  assert.isFalse(isSelf(participantObject));
138
143
  });
139
144
  });
145
+
146
+ describe('#sortByInitPriority', () => {
147
+ [
148
+ {
149
+ description: 'places "main" and "self" first when both appear',
150
+ input: ['atd-active', 'main', 'atd-unmuted', 'self'],
151
+ expected: ['main', 'self', 'atd-active', 'atd-unmuted'],
152
+ },
153
+ {
154
+ description: 'preserves original order of non-priority items',
155
+ input: ['atd-unmuted', 'atd-active', 'self'],
156
+ expected: ['self', 'atd-unmuted', 'atd-active'],
157
+ },
158
+ {
159
+ description: 'returns items unchanged when no priority items present',
160
+ input: ['atd-active', 'atd-unmuted'],
161
+ expected: ['atd-active', 'atd-unmuted'],
162
+ },
163
+ {
164
+ description: 'reorders when only priority items present',
165
+ input: ['self', 'main'],
166
+ expected: ['main', 'self'],
167
+ },
168
+ {
169
+ description: 'handles empty list',
170
+ input: [],
171
+ expected: [],
172
+ },
173
+ {
174
+ description: 'handles only some priority items present',
175
+ input: ['atd-active', 'main'],
176
+ expected: ['main', 'atd-active'],
177
+ },
178
+ {
179
+ description: 'handles single non-priority item',
180
+ input: ['atd-active'],
181
+ expected: ['atd-active'],
182
+ },
183
+ {
184
+ description: 'handles single priority item',
185
+ input: ['self'],
186
+ expected: ['self'],
187
+ },
188
+ ].forEach(({description, input, expected}) => {
189
+ it(description, () => {
190
+ const items = input.map((name) => ({name}));
191
+
192
+ const result = sortByInitPriority(items, DATA_SET_INIT_PRIORITY);
193
+
194
+ assert.deepEqual(
195
+ result.map((i) => i.name),
196
+ expected
197
+ );
198
+ });
199
+ });
200
+
201
+ it('should not mutate the original array', () => {
202
+ const items = [{name: DataSetNames.ATD_ACTIVE}, {name: DataSetNames.SELF}];
203
+ const originalOrder = items.map((i) => i.name);
204
+
205
+ sortByInitPriority(items, DATA_SET_INIT_PRIORITY);
206
+
207
+ assert.deepEqual(
208
+ items.map((i) => i.name),
209
+ originalOrder
210
+ );
211
+ });
212
+
213
+ it('should preserve extra properties on items', () => {
214
+ const items = [
215
+ {name: DataSetNames.ATD_ACTIVE, url: 'url1'},
216
+ {name: DataSetNames.SELF, url: 'url2'},
217
+ ];
218
+
219
+ const result = sortByInitPriority(items, DATA_SET_INIT_PRIORITY);
220
+
221
+ assert.deepEqual(result, [
222
+ {name: DataSetNames.SELF, url: 'url2'},
223
+ {name: DataSetNames.ATD_ACTIVE, url: 'url1'},
224
+ ]);
225
+ });
226
+ });
140
227
  });
@@ -36,6 +36,27 @@ describe('plugin-meetings', () => {
36
36
  uri: `https://locus-test.webex.com/locus/api/v1/loci/call`,
37
37
  body: 'foo'
38
38
  };
39
+
40
+ const hashTreeOptions = {
41
+ method: 'GET',
42
+ headers: {
43
+ trackingid: 'test',
44
+ 'retry-after': 1000,
45
+ },
46
+ uri: `https://locus-test.webex.com/locus/api/v1/loci/12345/session/abc/datasets/main/hashtree`,
47
+ body: undefined,
48
+ };
49
+
50
+ const syncOptions = {
51
+ method: 'POST',
52
+ headers: {
53
+ trackingid: 'test',
54
+ 'retry-after': 1000,
55
+ },
56
+ uri: `https://locus-test.webex.com/locus/api/v1/loci/12345/session/abc/datasets/main/sync`,
57
+ body: 'foo',
58
+ };
59
+
39
60
  const reason1 = new WebexHttpError.MethodNotAllowed({
40
61
  statusCode: 403,
41
62
  options: {
@@ -68,14 +89,194 @@ describe('plugin-meetings', () => {
68
89
  });
69
90
 
70
91
  it('calls handleRetryRequestLocusServiceError with correct retry time when locus service unavailable error', () => {
71
- interceptor.webex.request = sinon.stub().returns(Promise.resolve());
72
- const handleRetryStub = sinon.stub(interceptor, 'handleRetryRequestLocusServiceError');
92
+ interceptor.webex.request = sinon.stub().returns(Promise.resolve());
93
+ const handleRetryStub = sinon.stub(
94
+ interceptor,
95
+ 'handleRetryRequestLocusServiceError'
96
+ );
97
+ handleRetryStub.returns(Promise.resolve());
98
+
99
+ return interceptor.onResponseError(options, reason2).then(() => {
100
+ expect(handleRetryStub.calledWith(options, 1000)).to.be.true;
101
+ });
102
+ });
103
+
104
+ [429, 500, 502, 503, 504].forEach((statusCode) => {
105
+ it(`does not retry /hashtree requests on ${statusCode}`, () => {
106
+ const reason = new WebexHttpError.MethodNotAllowed({
107
+ statusCode,
108
+ options: {
109
+ headers: {trackingid: 'test', 'retry-after': 1000},
110
+ uri: hashTreeOptions.uri,
111
+ },
112
+ body: {error: `Fake ${statusCode}`},
113
+ });
114
+
115
+ const handleRetryStub = sinon.stub(
116
+ interceptor,
117
+ 'handleRetryRequestLocusServiceError'
118
+ );
119
+ handleRetryStub.returns(Promise.resolve());
120
+
121
+ return interceptor.onResponseError(hashTreeOptions, reason).then(
122
+ () => assert.fail('Expected promise to be rejected'),
123
+ (err) => {
124
+ expect(err).to.equal(reason);
125
+ expect(handleRetryStub.called).to.be.false;
126
+ handleRetryStub.restore();
127
+ }
128
+ );
129
+ });
130
+
131
+ it(`does not retry /sync requests on ${statusCode}`, () => {
132
+ const reason = new WebexHttpError.MethodNotAllowed({
133
+ statusCode,
134
+ options: {
135
+ headers: {trackingid: 'test', 'retry-after': 1000},
136
+ uri: syncOptions.uri,
137
+ },
138
+ body: {error: `Fake ${statusCode}`},
139
+ });
140
+
141
+ const handleRetryStub = sinon.stub(
142
+ interceptor,
143
+ 'handleRetryRequestLocusServiceError'
144
+ );
73
145
  handleRetryStub.returns(Promise.resolve());
74
146
 
75
- return interceptor.onResponseError(options, reason2).then(() => {
76
- expect(handleRetryStub.calledWith(options, 1000)).to.be.true;
147
+ return interceptor.onResponseError(syncOptions, reason).then(
148
+ () => assert.fail('Expected promise to be rejected'),
149
+ (err) => {
150
+ expect(err).to.equal(reason);
151
+ expect(handleRetryStub.called).to.be.false;
152
+ handleRetryStub.restore();
153
+ }
154
+ );
155
+ });
156
+ });
157
+
158
+ it('still retries other locus requests on 429', () => {
159
+ const reason429 = new WebexHttpError.MethodNotAllowed({
160
+ statusCode: 429,
161
+ options: {
162
+ headers: {trackingid: 'test', 'retry-after': 1000},
163
+ uri: options.uri,
164
+ },
165
+ body: {error: 'Too Many Requests'},
166
+ });
167
+
168
+ interceptor.webex.request = sinon.stub().returns(Promise.resolve());
169
+ const handleRetryStub = sinon.stub(
170
+ interceptor,
171
+ 'handleRetryRequestLocusServiceError'
172
+ );
173
+ handleRetryStub.returns(Promise.resolve());
77
174
 
175
+ return interceptor.onResponseError(options, reason429).then(() => {
176
+ expect(handleRetryStub.calledOnce).to.be.true;
177
+ handleRetryStub.restore();
178
+ });
179
+ });
180
+
181
+ it('still retries other locus requests on 503', () => {
182
+ interceptor.webex.request = sinon.stub().returns(Promise.resolve());
183
+ const handleRetryStub = sinon.stub(
184
+ interceptor,
185
+ 'handleRetryRequestLocusServiceError'
186
+ );
187
+ handleRetryStub.returns(Promise.resolve());
188
+
189
+ return interceptor.onResponseError(options, reason2).then(() => {
190
+ expect(handleRetryStub.calledOnce).to.be.true;
191
+ handleRetryStub.restore();
192
+ });
193
+ });
194
+
195
+ describe('URI parsing edge cases', () => {
196
+ const make503Reason = (uri) =>
197
+ new WebexHttpError.MethodNotAllowed({
198
+ statusCode: 503,
199
+ options: {headers: {trackingid: 'test', 'retry-after': 1000}, uri},
200
+ body: {error: 'Service Unavailable'},
201
+ });
202
+
203
+ const makeOptions = (uri) => ({
204
+ method: 'GET',
205
+ headers: {trackingid: 'test', 'retry-after': 1000},
206
+ uri,
207
+ body: undefined,
208
+ });
209
+
210
+ [
211
+ 'https://locus.webex.com/locus/api/v1/loci/123/session/abc/datasets/main/hashtree?rootHash=xyz',
212
+ 'https://locus.webex.com/locus/api/v1/loci/123/session/abc/datasets/main/sync?seq=5',
213
+ ].forEach((uri) => {
214
+ it(`skips retry even with query params: ${uri.split('/').pop()}`, () => {
215
+ const opts = makeOptions(uri);
216
+ const reason = make503Reason(uri);
217
+ const stub = sinon
218
+ .stub(interceptor, 'handleRetryRequestLocusServiceError')
219
+ .returns(Promise.resolve());
220
+
221
+ return interceptor.onResponseError(opts, reason).then(
222
+ () => assert.fail('Expected promise to be rejected'),
223
+ (err) => {
224
+ expect(err).to.equal(reason);
225
+ expect(stub.called).to.be.false;
226
+ stub.restore();
227
+ }
228
+ );
78
229
  });
230
+ });
231
+
232
+ [
233
+ 'https://locus.webex.com/locus/api/v1/loci/123/hashtree-v2',
234
+ 'https://locus.webex.com/locus/api/v1/loci/123/syncData',
235
+ 'https://locus.webex.com/locus/api/v1/loci/123/async',
236
+ 'https://locus.webex.com/locus/api/v1/loci/123/hashtree/metadata',
237
+ ].forEach((uri) => {
238
+ it(`still retries when path only partially matches: ${uri
239
+ .split('/')
240
+ .pop()}`, () => {
241
+ const opts = makeOptions(uri);
242
+ const reason = make503Reason(uri);
243
+ interceptor.webex.request = sinon.stub().returns(Promise.resolve());
244
+ const stub = sinon
245
+ .stub(interceptor, 'handleRetryRequestLocusServiceError')
246
+ .returns(Promise.resolve());
247
+
248
+ return interceptor.onResponseError(opts, reason).then(() => {
249
+ expect(stub.calledOnce).to.be.true;
250
+ stub.restore();
251
+ });
252
+ });
253
+ });
254
+
255
+ it('still retries when /hashtree is on a non-locus host', () => {
256
+ const uri = 'https://other-service.webex.com/api/v1/hashtree';
257
+ const opts = makeOptions(uri);
258
+ const reason = make503Reason(uri);
259
+
260
+ return interceptor.onResponseError(opts, reason).then(
261
+ () => assert.fail('Expected promise to be rejected'),
262
+ (err) => {
263
+ expect(err).to.equal(reason);
264
+ }
265
+ );
266
+ });
267
+
268
+ it('still retries when URI is malformed', () => {
269
+ const uri = 'not-a-valid-url';
270
+ const opts = makeOptions(uri);
271
+ const reason = make503Reason(uri);
272
+
273
+ return interceptor.onResponseError(opts, reason).then(
274
+ () => assert.fail('Expected promise to be rejected'),
275
+ (err) => {
276
+ expect(err).to.equal(reason);
277
+ }
278
+ );
279
+ });
79
280
  });
80
281
  });
81
282
 
@@ -9,6 +9,7 @@ describe('plugin-meetings', () => {
9
9
  describe('SimultaneousInterpretation', () => {
10
10
  let webex;
11
11
  let interpretation;
12
+ let mockMeeting;
12
13
 
13
14
  beforeEach(() => {
14
15
  // @ts-ignore
@@ -17,8 +18,17 @@ describe('plugin-meetings', () => {
17
18
  interpretation = new SimultaneousInterpretation({}, {parent: webex});
18
19
  interpretation.locusUrl = 'locusUrl';
19
20
  webex.request = sinon.stub().returns(Promise.resolve('REQUEST_RETURN_VALUE'));
20
- webex.meetings = {};
21
- webex.meetings.getMeetingByType = sinon.stub();
21
+ mockMeeting = {
22
+ locusInfo: {
23
+ handleLocusAPIResponse: sinon.stub(),
24
+ },
25
+ };
26
+ webex.meetings = {
27
+ getMeetingByType: sinon.stub(),
28
+ meetingCollection: {
29
+ getByKey: sinon.stub().returns(mockMeeting),
30
+ },
31
+ };
22
32
  });
23
33
 
24
34
  describe('#initialize', () => {
@@ -316,7 +326,8 @@ describe('plugin-meetings', () => {
316
326
  order : 0,
317
327
  isActive : true
318
328
  },];
319
- webex.request.returns(Promise.resolve({}));
329
+ const mockResponse = {body: {locus: {url: 'locusUrl'}}};
330
+ webex.request.returns(Promise.resolve(mockResponse));
320
331
 
321
332
  await interpretation.updateInterpreters(sampleData);
322
333
  assert.calledOnceWithExactly(webex.request, {
@@ -328,6 +339,11 @@ describe('plugin-meetings', () => {
328
339
  },
329
340
  },
330
341
  });
342
+ assert.calledOnceWithExactly(
343
+ mockMeeting.locusInfo.handleLocusAPIResponse,
344
+ mockMeeting,
345
+ mockResponse.body
346
+ );
331
347
  });
332
348
 
333
349
  it('rejects with error', async () => {
@@ -354,7 +370,8 @@ describe('plugin-meetings', () => {
354
370
  order: 0,
355
371
  selfParticipantId: '123',
356
372
  });
357
- webex.request.returns(Promise.resolve({}));
373
+ const mockResponse = {body: {locus: {url: 'locusUrl'}}};
374
+ webex.request.returns(Promise.resolve(mockResponse));
358
375
 
359
376
  await interpretation.changeDirection();
360
377
  assert.calledOnceWithExactly(webex.request, {
@@ -369,6 +386,11 @@ describe('plugin-meetings', () => {
369
386
  },
370
387
  },
371
388
  });
389
+ assert.calledOnceWithExactly(
390
+ mockMeeting.locusInfo.handleLocusAPIResponse,
391
+ mockMeeting,
392
+ mockResponse.body
393
+ );
372
394
  });
373
395
 
374
396
  it('request rejects with error', async () => {