@webex/plugin-meetings 3.12.0-next.7 → 3.12.0-next.70

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 (178) 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 +8 -3
  5. package/dist/breakouts/breakout.js.map +1 -1
  6. package/dist/breakouts/index.js +26 -2
  7. package/dist/breakouts/index.js.map +1 -1
  8. package/dist/config.js +2 -0
  9. package/dist/config.js.map +1 -1
  10. package/dist/constants.js +30 -7
  11. package/dist/constants.js.map +1 -1
  12. package/dist/controls-options-manager/constants.js +11 -1
  13. package/dist/controls-options-manager/constants.js.map +1 -1
  14. package/dist/controls-options-manager/index.js +38 -24
  15. package/dist/controls-options-manager/index.js.map +1 -1
  16. package/dist/controls-options-manager/util.js +91 -0
  17. package/dist/controls-options-manager/util.js.map +1 -1
  18. package/dist/hashTree/constants.js +13 -1
  19. package/dist/hashTree/constants.js.map +1 -1
  20. package/dist/hashTree/hashTreeParser.js +880 -382
  21. package/dist/hashTree/hashTreeParser.js.map +1 -1
  22. package/dist/hashTree/utils.js +42 -0
  23. package/dist/hashTree/utils.js.map +1 -1
  24. package/dist/index.js +7 -0
  25. package/dist/index.js.map +1 -1
  26. package/dist/interceptors/dataChannelAuthToken.js +75 -15
  27. package/dist/interceptors/dataChannelAuthToken.js.map +1 -1
  28. package/dist/interceptors/locusRetry.js +23 -8
  29. package/dist/interceptors/locusRetry.js.map +1 -1
  30. package/dist/interpretation/index.js +10 -1
  31. package/dist/interpretation/index.js.map +1 -1
  32. package/dist/interpretation/interpretation.types.js +7 -0
  33. package/dist/interpretation/interpretation.types.js.map +1 -0
  34. package/dist/interpretation/siLanguage.js +1 -1
  35. package/dist/locus-info/controlsUtils.js +4 -1
  36. package/dist/locus-info/controlsUtils.js.map +1 -1
  37. package/dist/locus-info/index.js +298 -87
  38. package/dist/locus-info/index.js.map +1 -1
  39. package/dist/locus-info/types.js +19 -0
  40. package/dist/locus-info/types.js.map +1 -1
  41. package/dist/media/index.js +3 -1
  42. package/dist/media/index.js.map +1 -1
  43. package/dist/media/properties.js +1 -0
  44. package/dist/media/properties.js.map +1 -1
  45. package/dist/meeting/in-meeting-actions.js +3 -1
  46. package/dist/meeting/in-meeting-actions.js.map +1 -1
  47. package/dist/meeting/index.js +1046 -689
  48. package/dist/meeting/index.js.map +1 -1
  49. package/dist/meeting/muteState.js +10 -1
  50. package/dist/meeting/muteState.js.map +1 -1
  51. package/dist/meeting/request.js +5 -2
  52. package/dist/meeting/request.js.map +1 -1
  53. package/dist/meeting/util.js +20 -2
  54. package/dist/meeting/util.js.map +1 -1
  55. package/dist/meeting-info/meeting-info-v2.js +2 -2
  56. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  57. package/dist/meetings/index.js +231 -78
  58. package/dist/meetings/index.js.map +1 -1
  59. package/dist/meetings/meetings.types.js +6 -1
  60. package/dist/meetings/meetings.types.js.map +1 -1
  61. package/dist/meetings/request.js +39 -0
  62. package/dist/meetings/request.js.map +1 -1
  63. package/dist/meetings/util.js +79 -5
  64. package/dist/meetings/util.js.map +1 -1
  65. package/dist/member/index.js +10 -0
  66. package/dist/member/index.js.map +1 -1
  67. package/dist/member/types.js.map +1 -1
  68. package/dist/member/util.js +3 -0
  69. package/dist/member/util.js.map +1 -1
  70. package/dist/metrics/constants.js +4 -1
  71. package/dist/metrics/constants.js.map +1 -1
  72. package/dist/multistream/codec/constants.js +63 -0
  73. package/dist/multistream/codec/constants.js.map +1 -0
  74. package/dist/multistream/mediaRequestManager.js +62 -15
  75. package/dist/multistream/mediaRequestManager.js.map +1 -1
  76. package/dist/multistream/receiveSlot.js +9 -0
  77. package/dist/multistream/receiveSlot.js.map +1 -1
  78. package/dist/reactions/reactions.type.js.map +1 -1
  79. package/dist/recording-controller/index.js +1 -3
  80. package/dist/recording-controller/index.js.map +1 -1
  81. package/dist/types/config.d.ts +2 -0
  82. package/dist/types/constants.d.ts +9 -1
  83. package/dist/types/controls-options-manager/constants.d.ts +6 -1
  84. package/dist/types/controls-options-manager/index.d.ts +10 -0
  85. package/dist/types/hashTree/constants.d.ts +2 -0
  86. package/dist/types/hashTree/hashTreeParser.d.ts +146 -17
  87. package/dist/types/hashTree/utils.d.ts +18 -0
  88. package/dist/types/index.d.ts +3 -0
  89. package/dist/types/interceptors/locusRetry.d.ts +4 -4
  90. package/dist/types/interpretation/interpretation.types.d.ts +10 -0
  91. package/dist/types/locus-info/index.d.ts +50 -6
  92. package/dist/types/locus-info/types.d.ts +21 -1
  93. package/dist/types/media/properties.d.ts +1 -0
  94. package/dist/types/meeting/in-meeting-actions.d.ts +2 -0
  95. package/dist/types/meeting/index.d.ts +78 -5
  96. package/dist/types/meeting/request.d.ts +1 -0
  97. package/dist/types/meeting/util.d.ts +8 -0
  98. package/dist/types/meetings/index.d.ts +30 -2
  99. package/dist/types/meetings/meetings.types.d.ts +15 -0
  100. package/dist/types/meetings/request.d.ts +14 -0
  101. package/dist/types/member/index.d.ts +1 -0
  102. package/dist/types/member/types.d.ts +1 -0
  103. package/dist/types/member/util.d.ts +1 -0
  104. package/dist/types/metrics/constants.d.ts +3 -0
  105. package/dist/types/multistream/codec/constants.d.ts +7 -0
  106. package/dist/types/multistream/mediaRequestManager.d.ts +22 -5
  107. package/dist/types/reactions/reactions.type.d.ts +3 -0
  108. package/dist/webinar/index.js +305 -159
  109. package/dist/webinar/index.js.map +1 -1
  110. package/package.json +22 -22
  111. package/src/aiEnableRequest/index.ts +16 -0
  112. package/src/breakouts/breakout.ts +3 -1
  113. package/src/breakouts/index.ts +31 -0
  114. package/src/config.ts +2 -0
  115. package/src/constants.ts +13 -2
  116. package/src/controls-options-manager/constants.ts +14 -1
  117. package/src/controls-options-manager/index.ts +47 -24
  118. package/src/controls-options-manager/util.ts +81 -1
  119. package/src/hashTree/constants.ts +16 -0
  120. package/src/hashTree/hashTreeParser.ts +580 -196
  121. package/src/hashTree/utils.ts +36 -0
  122. package/src/index.ts +6 -0
  123. package/src/interceptors/dataChannelAuthToken.ts +88 -12
  124. package/src/interceptors/locusRetry.ts +25 -4
  125. package/src/interpretation/index.ts +27 -9
  126. package/src/interpretation/interpretation.types.ts +11 -0
  127. package/src/locus-info/controlsUtils.ts +3 -1
  128. package/src/locus-info/index.ts +293 -97
  129. package/src/locus-info/types.ts +25 -1
  130. package/src/media/index.ts +3 -0
  131. package/src/media/properties.ts +1 -0
  132. package/src/meeting/in-meeting-actions.ts +4 -0
  133. package/src/meeting/index.ts +386 -48
  134. package/src/meeting/muteState.ts +10 -1
  135. package/src/meeting/request.ts +11 -0
  136. package/src/meeting/util.ts +21 -2
  137. package/src/meeting-info/meeting-info-v2.ts +4 -2
  138. package/src/meetings/index.ts +134 -44
  139. package/src/meetings/meetings.types.ts +19 -0
  140. package/src/meetings/request.ts +43 -0
  141. package/src/meetings/util.ts +97 -1
  142. package/src/member/index.ts +10 -0
  143. package/src/member/types.ts +1 -0
  144. package/src/member/util.ts +3 -0
  145. package/src/metrics/constants.ts +3 -0
  146. package/src/multistream/codec/constants.ts +58 -0
  147. package/src/multistream/mediaRequestManager.ts +119 -28
  148. package/src/multistream/receiveSlot.ts +18 -0
  149. package/src/reactions/reactions.type.ts +3 -0
  150. package/src/recording-controller/index.ts +1 -2
  151. package/src/webinar/index.ts +214 -36
  152. package/test/unit/spec/aiEnableRequest/index.ts +86 -0
  153. package/test/unit/spec/breakouts/breakout.ts +9 -3
  154. package/test/unit/spec/breakouts/index.ts +49 -0
  155. package/test/unit/spec/controls-options-manager/index.js +140 -29
  156. package/test/unit/spec/controls-options-manager/util.js +165 -0
  157. package/test/unit/spec/hashTree/hashTreeParser.ts +1838 -180
  158. package/test/unit/spec/hashTree/utils.ts +125 -1
  159. package/test/unit/spec/interceptors/dataChannelAuthToken.ts +196 -0
  160. package/test/unit/spec/interceptors/locusRetry.ts +205 -4
  161. package/test/unit/spec/interpretation/index.ts +26 -4
  162. package/test/unit/spec/locus-info/controlsUtils.js +172 -57
  163. package/test/unit/spec/locus-info/index.js +487 -81
  164. package/test/unit/spec/media/index.ts +31 -0
  165. package/test/unit/spec/meeting/in-meeting-actions.ts +2 -0
  166. package/test/unit/spec/meeting/index.js +1240 -37
  167. package/test/unit/spec/meeting/muteState.js +81 -0
  168. package/test/unit/spec/meeting/request.js +12 -0
  169. package/test/unit/spec/meeting/utils.js +33 -0
  170. package/test/unit/spec/meeting-info/meetinginfov2.js +19 -10
  171. package/test/unit/spec/meetings/index.js +360 -10
  172. package/test/unit/spec/meetings/request.js +141 -0
  173. package/test/unit/spec/meetings/utils.js +189 -0
  174. package/test/unit/spec/member/index.js +7 -0
  175. package/test/unit/spec/member/util.js +24 -0
  176. package/test/unit/spec/multistream/mediaRequestManager.ts +501 -37
  177. package/test/unit/spec/recording-controller/index.js +9 -8
  178. package/test/unit/spec/webinar/index.ts +329 -28
@@ -60,3 +60,39 @@ export const deleteNestedObjectsWithHtMeta = (
60
60
  }
61
61
  }
62
62
  };
63
+
64
+ /**
65
+ * Reorders items so that those matching the given priority list come first (in priority order),
66
+ * followed by everything else in their original order.
67
+ *
68
+ * @param {Array<T>} items - The items to reorder
69
+ * @param {string[]} priority - Ordered list of names that should come first
70
+ * @returns {Array<T>} A new array with prioritized items first
71
+ */
72
+ export function sortByInitPriority<T extends {name: string}>(items: T[], priority: string[]): T[] {
73
+ const prioritized = priority
74
+ .map((name) => items.find((item) => item.name === name))
75
+ .filter(Boolean) as T[];
76
+ const rest = items.filter((item) => !priority.includes(item.name));
77
+
78
+ return [...prioritized, ...rest];
79
+ }
80
+
81
+ /**
82
+ * Sleeps for the specified amount of milliseconds
83
+ *
84
+ * @param {number} ms amount of milliseconds to sleep
85
+ * @returns {Promise<void>} A promise that resolves after the specified delay
86
+ */
87
+ export function sleep(ms: number): Promise<void> {
88
+ if (ms <= 0) {
89
+ return Promise.resolve();
90
+ }
91
+
92
+ return new Promise((resolve) => {
93
+ // start a timer that will resolve the promise after the specified delay
94
+ setTimeout(() => {
95
+ resolve();
96
+ }, ms);
97
+ });
98
+ }
package/src/index.ts CHANGED
@@ -65,6 +65,12 @@ export * as REACTIONS from './reactions/reactions';
65
65
  export * as sdkAnnotationTypes from './annotation/annotation.types';
66
66
  export * as MeetingInfoV2 from './meeting-info/meeting-info-v2';
67
67
  export {type Reaction} from './reactions/reactions.type';
68
+ export {SitePreferenceSelectOption} from './meetings/meetings.types';
69
+ export type {
70
+ FetchSitePreferencesMeViaSiteOptions,
71
+ SitePreferencesResponse,
72
+ } from './meetings/meetings.types';
73
+ export type {Interpreter, InterpreterUsingResource} from './interpretation/interpretation.types';
68
74
 
69
75
  export {
70
76
  CaptchaError,
@@ -3,13 +3,11 @@
3
3
  */
4
4
 
5
5
  import {Interceptor} from '@webex/http-core';
6
+ import LLMChannel from '@webex/internal-plugin-llm';
6
7
  import LoggerProxy from '../common/logs/logger-proxy';
7
8
  import {DATA_CHANNEL_AUTH_HEADER, MAX_RETRY, RETRY_INTERVAL, RETRY_KEY} from './constant';
8
9
  import {isJwtTokenExpired} from './utils';
9
-
10
- /*!
11
- * Copyright (c) 2015-2026 Cisco Systems, Inc. See LICENSE file.
12
- */
10
+ import {LLM_DEFAULT_SESSION, LLM_PRACTICE_SESSION, LOCUS_URL} from '../constants';
13
11
 
14
12
  const retryCountMap = new Map();
15
13
  interface HttpLikeError extends Error {
@@ -20,7 +18,7 @@ interface HttpLikeError extends Error {
20
18
  * @class
21
19
  */
22
20
  export default class DataChannelAuthTokenInterceptor extends Interceptor {
23
- private _refreshDataChannelToken: () => Promise<string>;
21
+ private _refreshDataChannelToken: (requestUrl?: string) => Promise<string>;
24
22
  private _isDataChannelTokenEnabled: () => Promise<boolean>;
25
23
  constructor(options) {
26
24
  super(options);
@@ -42,13 +40,91 @@ export default class DataChannelAuthTokenInterceptor extends Interceptor {
42
40
  return this.internal.llm.isDataChannelTokenEnabled();
43
41
  },
44
42
 
45
- refreshDataChannelToken: async () => {
46
- // @ts-ignore
47
- const {body} = await this.internal.llm.refreshDataChannelToken();
48
- const {datachannelToken, dataChannelTokenType} = body ?? {};
43
+ // Route refresh by request URL in two steps:
44
+ // 1) Match active LLM sessions (supports non-default/multiple sessions)
45
+ // 2) If no session matches, resolve locusUrl and refresh via owning meeting
46
+ // If neither route matches, fall back to the default-session refresh.
47
+ refreshDataChannelToken: async (requestUrl?: string) => {
48
+ let sessionId;
49
+ let meeting;
49
50
 
51
+ if (typeof requestUrl === 'string') {
52
+ // @ts-ignore
53
+ sessionId = this.internal.llm.getSessionIdByDatachannelUrl?.(requestUrl);
54
+
55
+ if (!sessionId) {
56
+ // @ts-ignore
57
+ const locusUrl = this.internal.llm.getLocusUrlByDatachannelUrl?.(requestUrl);
58
+
59
+ if (locusUrl) {
60
+ // @ts-ignore
61
+ meeting = this.meetings?.getMeetingByType?.(LOCUS_URL, locusUrl);
62
+ }
63
+ }
64
+
65
+ if (!meeting) {
66
+ // Fallback: registerAndConnect() pre-populates datachannelUrl in
67
+ // connections before calling register(), so getSessionIdByDatachannelUrl
68
+ // above should normally resolve. This scan covers any residual gap
69
+ // where connections are not yet populated (e.g. if setRefreshHandler
70
+ // is called on a session that has no connection entry at all).
71
+ // @ts-ignore
72
+ const allMeetings = this.meetings?.getAllMeetings?.() || {};
73
+
74
+ meeting = Object.values(allMeetings).find((activeMeeting: any) => {
75
+ const info = activeMeeting?.locusInfo?.info || {};
76
+
77
+ return (
78
+ (info.practiceSessionDatachannelUrl &&
79
+ LLMChannel.matchesDatachannelRequestUrl(
80
+ requestUrl,
81
+ info.practiceSessionDatachannelUrl
82
+ )) ||
83
+ (info.datachannelUrl &&
84
+ LLMChannel.matchesDatachannelRequestUrl(requestUrl, info.datachannelUrl))
85
+ );
86
+ });
87
+
88
+ if (!sessionId) {
89
+ // @ts-ignore
90
+ const info = meeting?.locusInfo?.info || {};
91
+
92
+ if (
93
+ info.practiceSessionDatachannelUrl &&
94
+ LLMChannel.matchesDatachannelRequestUrl(
95
+ requestUrl,
96
+ info.practiceSessionDatachannelUrl
97
+ )
98
+ ) {
99
+ sessionId = LLM_PRACTICE_SESSION;
100
+ } else if (
101
+ info.datachannelUrl &&
102
+ LLMChannel.matchesDatachannelRequestUrl(requestUrl, info.datachannelUrl)
103
+ ) {
104
+ sessionId = LLM_DEFAULT_SESSION;
105
+ }
106
+ }
107
+ }
108
+ }
109
+
110
+ let result;
111
+ if (meeting) {
112
+ result = await meeting.refreshDataChannelToken();
113
+ } else {
114
+ // @ts-ignore
115
+ result = await this.internal.llm.refreshDataChannelToken(sessionId);
116
+ }
117
+
118
+ if (!result?.body) {
119
+ throw new Error('DataChannel token refresh returned no payload');
120
+ }
121
+ const {datachannelToken, dataChannelTokenType} = result.body;
122
+ const tokenStoreKey = dataChannelTokenType || sessionId;
123
+ const ownerMeetingId =
124
+ // @ts-ignore
125
+ meeting?.id || this.internal.llm.getOwnerMeetingId?.(tokenStoreKey);
50
126
  // @ts-ignore
51
- this.internal.llm.setDatachannelToken(datachannelToken, dataChannelTokenType);
127
+ this.internal.llm.setDatachannelToken(datachannelToken, tokenStoreKey, ownerMeetingId);
52
128
 
53
129
  return datachannelToken;
54
130
  },
@@ -87,7 +163,7 @@ export default class DataChannelAuthTokenInterceptor extends Interceptor {
87
163
 
88
164
  if (isJwtTokenExpired(token)) {
89
165
  try {
90
- const newToken = await this._refreshDataChannelToken();
166
+ const newToken = await this._refreshDataChannelToken(options.uri || options.url);
91
167
  options.headers[DATA_CHANNEL_AUTH_HEADER] = newToken;
92
168
  } catch (e) {
93
169
  LoggerProxy.logger.warn(`DataChannelAuthTokenInterceptor: refresh failed: ${e.message}`);
@@ -144,7 +220,7 @@ export default class DataChannelAuthTokenInterceptor extends Interceptor {
144
220
  setTimeout(async () => {
145
221
  const key = this.getRetryKey(options);
146
222
  try {
147
- const newToken = await this._refreshDataChannelToken();
223
+ const newToken = await this._refreshDataChannelToken(options.uri || options.url);
148
224
 
149
225
  options.headers[DATA_CHANNEL_AUTH_HEADER] = newToken;
150
226
 
@@ -18,12 +18,33 @@ export default class LocusRetryStatusInterceptor extends Interceptor {
18
18
  }
19
19
 
20
20
  /**
21
- * Handle response errors
22
- * @param {Object} options
23
- * @param {WebexHttpError} reason
24
- * @returns {Promise<WebexHttpError>}
21
+ * Check whether a URI is a Locus /hashtree or /sync endpoint.
22
+ * @param {string} uri
23
+ * @returns {boolean}
25
24
  */
25
+ private static isLocusHashtreeOrSync(uri: string): boolean {
26
+ try {
27
+ const {pathname} = new URL(uri);
28
+
29
+ return (
30
+ pathname.includes('/locus/') &&
31
+ (pathname.endsWith('/hashtree') || pathname.endsWith('/sync'))
32
+ );
33
+ } catch {
34
+ return false;
35
+ }
36
+ }
37
+
26
38
  onResponseError(options, reason) {
39
+ // Don't retry /hashtree or /sync calls for 429 or any 5xx — during a sync storm retries
40
+ // make things worse. The normal sync timers will handle recovery for these endpoints.
41
+ if (
42
+ (reason.statusCode === 429 || reason.statusCode >= 500) &&
43
+ LocusRetryStatusInterceptor.isLocusHashtreeOrSync(options.uri)
44
+ ) {
45
+ return Promise.reject(reason);
46
+ }
47
+
27
48
  if ((reason.statusCode === 503 || reason.statusCode === 429) && options.uri.includes('locus')) {
28
49
  const hasRetriedLocusRequest = rateLimitExpiryTime.get(this);
29
50
  const retryAfterTime = options.headers['retry-after'] || 2000;
@@ -4,8 +4,10 @@
4
4
  import {WebexPlugin} from '@webex/webex-core';
5
5
  import LoggerProxy from '../common/logs/logger-proxy';
6
6
  import {HTTP_VERBS, INTERPRETATION, LOCUSEVENT, MEETINGS} from '../constants';
7
+ import MeetingUtil from '../meeting/util';
7
8
 
8
9
  import SILanguageCollection from './collection';
10
+ import {Interpreter} from './interpretation.types';
9
11
 
10
12
  /**
11
13
  * @class SimultaneousInterpretation
@@ -182,7 +184,9 @@ const SimultaneousInterpretation = WebexPlugin.extend({
182
184
  * @param {Array} interpreters
183
185
  * @returns {Promise}
184
186
  */
185
- updateInterpreters(interpreters) {
187
+ updateInterpreters(interpreters: Interpreter[]) {
188
+ const meeting = this.webex.meetings.meetingCollection.getByKey('locusUrl', this.locusUrl);
189
+
186
190
  return this.request({
187
191
  method: HTTP_VERBS.PATCH,
188
192
  uri: `${this.locusUrl}/controls`,
@@ -191,10 +195,16 @@ const SimultaneousInterpretation = WebexPlugin.extend({
191
195
  interpreters,
192
196
  },
193
197
  },
194
- }).catch((error) => {
195
- LoggerProxy.logger.error('Meeting:interpretation#updateInterpreters failed', error);
196
- throw error;
197
- });
198
+ })
199
+ .then((response) => {
200
+ MeetingUtil.updateLocusFromApiResponse(meeting, response);
201
+
202
+ return response;
203
+ })
204
+ .catch((error) => {
205
+ LoggerProxy.logger.error('Meeting:interpretation#updateInterpreters failed', error);
206
+ throw error;
207
+ });
198
208
  },
199
209
  /**
200
210
  * Change direction of interpretation for an interpreter participant
@@ -209,6 +219,8 @@ const SimultaneousInterpretation = WebexPlugin.extend({
209
219
  return Promise.reject(new Error('Missing self participant id'));
210
220
  }
211
221
 
222
+ const meeting = this.webex.meetings.meetingCollection.getByKey('locusUrl', this.locusUrl);
223
+
212
224
  return this.request({
213
225
  method: HTTP_VERBS.PATCH,
214
226
  uri: `${this.locusUrl}/participant/${this.selfParticipantId}/controls`,
@@ -220,10 +232,16 @@ const SimultaneousInterpretation = WebexPlugin.extend({
220
232
  order: this.order,
221
233
  },
222
234
  },
223
- }).catch((error) => {
224
- LoggerProxy.logger.error('Meeting:interpretation#changeDirection failed', error);
225
- throw error;
226
- });
235
+ })
236
+ .then((response) => {
237
+ MeetingUtil.updateLocusFromApiResponse(meeting, response);
238
+
239
+ return response;
240
+ })
241
+ .catch((error) => {
242
+ LoggerProxy.logger.error('Meeting:interpretation#changeDirection failed', error);
243
+ throw error;
244
+ });
227
245
  },
228
246
  /**
229
247
  * Sets up a listener for handoff requests from mercury
@@ -0,0 +1,11 @@
1
+ export type InterpreterUsingResource = {
2
+ id: string;
3
+ email?: string;
4
+ };
5
+
6
+ export type Interpreter = {
7
+ order: number;
8
+ sourceLanguage: string;
9
+ targetLanguage: string;
10
+ usingResource: InterpreterUsingResource;
11
+ };
@@ -26,7 +26,9 @@ ControlsUtils.parse = (controls: any) => {
26
26
  modifiedBy: ControlsUtils.getId(controls),
27
27
  paused: controls.record.paused ? controls.record.paused : false,
28
28
  recording: controls.record.recording,
29
- lastModified: controls.record.meta.lastModified,
29
+ lastModified: controls.record.meta?.lastModified,
30
+ modifiedByServiceAppName: controls.record.meta?.modifiedByServiceAppName,
31
+ modifiedByServiceAppId: controls.record.meta?.modifiedByServiceAppId,
30
32
  };
31
33
  }
32
34