@webex/internal-plugin-llm 3.11.0-next.10 → 3.11.0-next.12

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.
package/README.md CHANGED
@@ -79,8 +79,8 @@ llm.on(`event:${sessionB}`, (envelope) => {
79
79
  });
80
80
 
81
81
  // Optional: store/retrieve token by token type
82
- webex.internal.llm.setDatachannelToken(datachannelToken, 'DEFAULT');
83
- webex.internal.llm.getDatachannelToken('DEFAULT');
82
+ webex.internal.llm.setDatachannelToken(datachannelToken, 'llm-default-session');
83
+ webex.internal.llm.getDatachannelToken('llm-default-session');
84
84
 
85
85
  // Optional: inject token refresh handler
86
86
  webex.internal.llm.setRefreshHandler(async () => {
@@ -88,7 +88,7 @@ webex.internal.llm.setRefreshHandler(async () => {
88
88
  return {
89
89
  body: {
90
90
  datachannelToken: '<refreshed-jwt-token>',
91
- datachannelTokenType: 'DEFAULT',
91
+ datachannelTokenType: 'llm-default-session',
92
92
  },
93
93
  };
94
94
  });
package/dist/constants.js CHANGED
@@ -4,9 +4,14 @@ var _Object$defineProperty = require("@babel/runtime-corejs2/core-js/object/defi
4
4
  _Object$defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
- exports.LLM_DEFAULT_SESSION = exports.LLM = exports.DATA_CHANNEL_WITH_JWT_TOKEN = void 0;
7
+ exports.SUBSCRIPTION_AWARE_SUBCHANNELS_PARAM = exports.LLM_DEFAULT_SESSION = exports.LLM = exports.DATA_CHNANEL_TYPE = exports.DATA_CHANNEL_WITH_JWT_TOKEN = exports.AWARE_DATA_CHANNEL = void 0;
8
8
  // eslint-disable-next-line import/prefer-default-export
9
9
  var LLM = exports.LLM = 'llm';
10
10
  var LLM_DEFAULT_SESSION = exports.LLM_DEFAULT_SESSION = 'llm-default-session';
11
11
  var DATA_CHANNEL_WITH_JWT_TOKEN = exports.DATA_CHANNEL_WITH_JWT_TOKEN = 'data-channel-with-jwt-token';
12
+ var SUBSCRIPTION_AWARE_SUBCHANNELS_PARAM = exports.SUBSCRIPTION_AWARE_SUBCHANNELS_PARAM = 'subscriptionAwareSubchannels';
13
+ var DATA_CHNANEL_TYPE = exports.DATA_CHNANEL_TYPE = {
14
+ TRANSCRIPTION: 'transcription'
15
+ };
16
+ var AWARE_DATA_CHANNEL = exports.AWARE_DATA_CHANNEL = [DATA_CHNANEL_TYPE.TRANSCRIPTION];
12
17
  //# sourceMappingURL=constants.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["LLM","exports","LLM_DEFAULT_SESSION","DATA_CHANNEL_WITH_JWT_TOKEN"],"sources":["constants.ts"],"sourcesContent":["// eslint-disable-next-line import/prefer-default-export\nexport const LLM = 'llm';\n\nexport const LLM_DEFAULT_SESSION = 'llm-default-session';\n\nexport const DATA_CHANNEL_WITH_JWT_TOKEN = 'data-channel-with-jwt-token';\n"],"mappings":";;;;;;;AAAA;AACO,IAAMA,GAAG,GAAAC,OAAA,CAAAD,GAAA,GAAG,KAAK;AAEjB,IAAME,mBAAmB,GAAAD,OAAA,CAAAC,mBAAA,GAAG,qBAAqB;AAEjD,IAAMC,2BAA2B,GAAAF,OAAA,CAAAE,2BAAA,GAAG,6BAA6B","ignoreList":[]}
1
+ {"version":3,"names":["LLM","exports","LLM_DEFAULT_SESSION","DATA_CHANNEL_WITH_JWT_TOKEN","SUBSCRIPTION_AWARE_SUBCHANNELS_PARAM","DATA_CHNANEL_TYPE","TRANSCRIPTION","AWARE_DATA_CHANNEL"],"sources":["constants.ts"],"sourcesContent":["// eslint-disable-next-line import/prefer-default-export\nexport const LLM = 'llm';\n\nexport const LLM_DEFAULT_SESSION = 'llm-default-session';\n\nexport const DATA_CHANNEL_WITH_JWT_TOKEN = 'data-channel-with-jwt-token';\n\nexport const SUBSCRIPTION_AWARE_SUBCHANNELS_PARAM = 'subscriptionAwareSubchannels';\n\nexport const DATA_CHNANEL_TYPE = {\n TRANSCRIPTION: 'transcription',\n};\n\nexport const AWARE_DATA_CHANNEL = [DATA_CHNANEL_TYPE.TRANSCRIPTION];\n"],"mappings":";;;;;;;AAAA;AACO,IAAMA,GAAG,GAAAC,OAAA,CAAAD,GAAA,GAAG,KAAK;AAEjB,IAAME,mBAAmB,GAAAD,OAAA,CAAAC,mBAAA,GAAG,qBAAqB;AAEjD,IAAMC,2BAA2B,GAAAF,OAAA,CAAAE,2BAAA,GAAG,6BAA6B;AAEjE,IAAMC,oCAAoC,GAAAH,OAAA,CAAAG,oCAAA,GAAG,8BAA8B;AAE3E,IAAMC,iBAAiB,GAAAJ,OAAA,CAAAI,iBAAA,GAAG;EAC/BC,aAAa,EAAE;AACjB,CAAC;AAEM,IAAMC,kBAAkB,GAAAN,OAAA,CAAAM,kBAAA,GAAG,CAACF,iBAAiB,CAACC,aAAa,CAAC","ignoreList":[]}
package/dist/llm.js CHANGED
@@ -132,17 +132,35 @@ var LLMChannel = exports.default = /*#__PURE__*/function (_ref) {
132
132
  */
133
133
  (0, _defineProperty3.default)(_this, "registerAndConnect", function (locusUrl, datachannelUrl, datachannelToken) {
134
134
  var sessionId = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : _constants.LLM_DEFAULT_SESSION;
135
- return _this.register(datachannelUrl, datachannelToken, sessionId).then(function () {
136
- if (!locusUrl || !datachannelUrl) return undefined;
137
-
138
- // Get or create connection data
139
- var sessionData = _this.connections.get(sessionId) || {};
140
- sessionData.locusUrl = locusUrl;
141
- sessionData.datachannelUrl = datachannelUrl;
142
- sessionData.datachannelToken = datachannelToken;
143
- _this.connections.set(sessionId, sessionData);
144
- return _this.connect(sessionData.webSocketUrl, sessionId);
145
- });
135
+ return _this.register(datachannelUrl, datachannelToken, sessionId).then(/*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee2() {
136
+ var sessionData, isDataChannelTokenEnabled, connectUrl;
137
+ return _regenerator.default.wrap(function (_context2) {
138
+ while (1) switch (_context2.prev = _context2.next) {
139
+ case 0:
140
+ if (!(!locusUrl || !datachannelUrl)) {
141
+ _context2.next = 1;
142
+ break;
143
+ }
144
+ return _context2.abrupt("return", undefined);
145
+ case 1:
146
+ // Get or create connection data
147
+ sessionData = _this.connections.get(sessionId) || {};
148
+ sessionData.locusUrl = locusUrl;
149
+ sessionData.datachannelUrl = datachannelUrl;
150
+ sessionData.datachannelToken = datachannelToken;
151
+ _this.connections.set(sessionId, sessionData);
152
+ _context2.next = 2;
153
+ return _this.isDataChannelTokenEnabled();
154
+ case 2:
155
+ isDataChannelTokenEnabled = _context2.sent;
156
+ connectUrl = isDataChannelTokenEnabled ? LLMChannel.buildUrlWithAwareSubchannels(sessionData.webSocketUrl, _constants.AWARE_DATA_CHANNEL) : sessionData.webSocketUrl;
157
+ return _context2.abrupt("return", _this.connect(connectUrl, sessionId));
158
+ case 3:
159
+ case "end":
160
+ return _context2.stop();
161
+ }
162
+ }, _callee2);
163
+ })));
146
164
  });
147
165
  /**
148
166
  * Tells if LLM socket is connected
@@ -189,7 +207,8 @@ var LLMChannel = exports.default = /*#__PURE__*/function (_ref) {
189
207
  * @param {DataChannelTokenType} dataChannelTokenType
190
208
  * @returns {string} data channel token
191
209
  */
192
- (0, _defineProperty3.default)(_this, "getDatachannelToken", function (dataChannelTokenType) {
210
+ (0, _defineProperty3.default)(_this, "getDatachannelToken", function () {
211
+ var dataChannelTokenType = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _llm.DataChannelTokenType.Default;
193
212
  return _this.datachannelTokens[dataChannelTokenType];
194
213
  });
195
214
  /**
@@ -198,7 +217,8 @@ var LLMChannel = exports.default = /*#__PURE__*/function (_ref) {
198
217
  * @param {DataChannelTokenType} dataChannelTokenType
199
218
  * @returns {void}
200
219
  */
201
- (0, _defineProperty3.default)(_this, "setDatachannelToken", function (datachannelToken, dataChannelTokenType) {
220
+ (0, _defineProperty3.default)(_this, "setDatachannelToken", function (datachannelToken) {
221
+ var dataChannelTokenType = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _llm.DataChannelTokenType.Default;
202
222
  _this.datachannelTokens[dataChannelTokenType] = datachannelToken;
203
223
  });
204
224
  /**
@@ -212,6 +232,7 @@ var LLMChannel = exports.default = /*#__PURE__*/function (_ref) {
212
232
  return _this.disconnect(options, sessionId).then(function () {
213
233
  // Clean up sessions data
214
234
  _this.connections.delete(sessionId);
235
+ _this.datachannelTokens[sessionId] = undefined;
215
236
  });
216
237
  });
217
238
  /**
@@ -223,6 +244,7 @@ var LLMChannel = exports.default = /*#__PURE__*/function (_ref) {
223
244
  return _this.disconnectAll(options).then(function () {
224
245
  // Clean up all connection data
225
246
  _this.connections.clear();
247
+ _this.resetDatachannelTokens();
226
248
  });
227
249
  });
228
250
  /**
@@ -236,15 +258,26 @@ var LLMChannel = exports.default = /*#__PURE__*/function (_ref) {
236
258
  }
237
259
  (0, _inherits2.default)(LLMChannel, _ref);
238
260
  return (0, _createClass2.default)(LLMChannel, [{
239
- key: "setRefreshHandler",
261
+ key: "resetDatachannelTokens",
240
262
  value:
263
+ /**
264
+ * Resets all data‑channel tokens to their initial undefined values.
265
+ * Used when leaving or disconnecting from a meeting.
266
+ * @returns {void}
267
+ */
268
+ function resetDatachannelTokens() {
269
+ this.datachannelTokens = (0, _defineProperty3.default)((0, _defineProperty3.default)({}, _llm.DataChannelTokenType.Default, undefined), _llm.DataChannelTokenType.PracticeSession, undefined);
270
+ }
271
+
241
272
  /**
242
273
  * Set the handler used to refresh the DataChannel token
243
274
  *
244
275
  * @param {function} handler - Function that returns a refreshed token
245
276
  * @returns {void}
246
277
  */
247
- function setRefreshHandler(handler) {
278
+ }, {
279
+ key: "setRefreshHandler",
280
+ value: function setRefreshHandler(handler) {
248
281
  this.refreshHandler = handler;
249
282
  }
250
283
 
@@ -257,35 +290,34 @@ var LLMChannel = exports.default = /*#__PURE__*/function (_ref) {
257
290
  }, {
258
291
  key: "refreshDataChannelToken",
259
292
  value: (function () {
260
- var _refreshDataChannelToken = (0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee2() {
261
- var error, res, _t;
262
- return _regenerator.default.wrap(function (_context2) {
263
- while (1) switch (_context2.prev = _context2.next) {
293
+ var _refreshDataChannelToken = (0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee3() {
294
+ var res, _t;
295
+ return _regenerator.default.wrap(function (_context3) {
296
+ while (1) switch (_context3.prev = _context3.next) {
264
297
  case 0:
265
298
  if (this.refreshHandler) {
266
- _context2.next = 1;
299
+ _context3.next = 1;
267
300
  break;
268
301
  }
269
- error = new Error('LLM refreshHandler is not set');
270
- this.logger.error("Error refreshing DataChannel token: ".concat(error.message));
271
- throw error;
302
+ this.logger.warn('llm#refreshDataChannelToken --> LLM refreshHandler is not set, skipping token refresh');
303
+ return _context3.abrupt("return", null);
272
304
  case 1:
273
- _context2.prev = 1;
274
- _context2.next = 2;
305
+ _context3.prev = 1;
306
+ _context3.next = 2;
275
307
  return this.refreshHandler();
276
308
  case 2:
277
- res = _context2.sent;
278
- return _context2.abrupt("return", res);
309
+ res = _context3.sent;
310
+ return _context3.abrupt("return", res);
279
311
  case 3:
280
- _context2.prev = 3;
281
- _t = _context2["catch"](1);
282
- this.logger.error("Error refreshing DataChannel token: ".concat(_t));
283
- throw _t;
312
+ _context3.prev = 3;
313
+ _t = _context3["catch"](1);
314
+ this.logger.warn("llm#refreshDataChannelToken --> DataChannel token refresh failed (likely locus changed or participant left): ".concat((_t === null || _t === void 0 ? void 0 : _t.message) || _t));
315
+ return _context3.abrupt("return", null);
284
316
  case 4:
285
317
  case "end":
286
- return _context2.stop();
318
+ return _context3.stop();
287
319
  }
288
- }, _callee2, this, [[1, 3]]);
320
+ }, _callee3, this, [[1, 3]]);
289
321
  }));
290
322
  function refreshDataChannelToken() {
291
323
  return _refreshDataChannelToken.apply(this, arguments);
@@ -303,6 +335,19 @@ var LLMChannel = exports.default = /*#__PURE__*/function (_ref) {
303
335
  // @ts-ignore
304
336
  return this.webex.internal.feature.getFeature('developer', _constants.DATA_CHANNEL_WITH_JWT_TOKEN);
305
337
  }
338
+
339
+ /**
340
+ * Builds a WebSocket URL with the `subscriptionAwareSubchannels` query parameter.
341
+ *
342
+ * @param {string} baseUrl - The original WebSocket URL.
343
+ * @param {string[]} subchannels - List of subchannels to declare as subscription-aware.
344
+ * @returns {string} The final URL with updated query parameters.
345
+ */
306
346
  }]);
307
347
  }(_internalPluginMercury.default);
348
+ (0, _defineProperty3.default)(LLMChannel, "buildUrlWithAwareSubchannels", function (baseUrl, subchannels) {
349
+ var urlObj = new URL(baseUrl);
350
+ urlObj.searchParams.set(_constants.SUBSCRIPTION_AWARE_SUBCHANNELS_PARAM, subchannels.join(','));
351
+ return urlObj.toString();
352
+ });
308
353
  //# sourceMappingURL=llm.js.map
package/dist/llm.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"names":["_internalPluginMercury","_interopRequireDefault","require","_constants","_llm","_callSuper","t","o","e","_getPrototypeOf2","default","_possibleConstructorReturn2","_isNativeReflectConstruct","_Reflect$construct","constructor","apply","Boolean","prototype","valueOf","call","config","exports","llm","pingInterval","process","env","MERCURY_PING_INTERVAL","pongTimeout","MERCURY_PONG_TIMEOUT","backoffTimeMax","MERCURY_BACKOFF_TIME_MAX","backoffTimeReset","MERCURY_BACKOFF_TIME_RESET","forceCloseDelay","MERCURY_FORCE_CLOSE_DELAY","LLMChannel","_ref","_this","_classCallCheck2","_len","arguments","length","args","Array","_key","concat","_defineProperty3","LLM","LLM_DEFAULT_SESSION","_map","DataChannelTokenType","Default","undefined","PracticeSession","_ref2","_asyncToGenerator2","_regenerator","mark","_callee","llmSocketUrl","datachannelToken","sessionId","isDataChannelTokenEnabled","_args","wrap","_context","prev","next","sent","abrupt","request","method","url","body","deviceUrl","webex","internal","device","headers","then","res","sessionData","connections","get","webSocketUrl","binding","set","catch","error","logger","stop","_x","_x2","locusUrl","datachannelUrl","register","connect","socket","getSocket","connected","dataChannelTokenType","datachannelTokens","options","disconnect","delete","disconnectAll","clear","_inherits2","_createClass2","key","value","setRefreshHandler","handler","refreshHandler","_refreshDataChannelToken","_callee2","_t","_context2","Error","message","refreshDataChannelToken","feature","getFeature","DATA_CHANNEL_WITH_JWT_TOKEN","Mercury"],"sources":["llm.ts"],"sourcesContent":["/* eslint-disable consistent-return */\n\nimport Mercury from '@webex/internal-plugin-mercury';\n\nimport {LLM, DATA_CHANNEL_WITH_JWT_TOKEN, LLM_DEFAULT_SESSION} from './constants';\n// eslint-disable-next-line no-unused-vars\nimport {ILLMChannel, DataChannelTokenType} from './llm.types';\n\nexport const config = {\n llm: {\n /**\n * Milliseconds between pings sent up the socket\n * @type {number}\n */\n pingInterval: process.env.MERCURY_PING_INTERVAL || 15000,\n /**\n * Milliseconds to wait for a pong before declaring the connection dead\n * @type {number}\n */\n pongTimeout: process.env.MERCURY_PONG_TIMEOUT || 14000,\n /**\n * Maximum milliseconds between connection attempts\n * @type {Number}\n */\n backoffTimeMax: process.env.MERCURY_BACKOFF_TIME_MAX || 32000,\n /**\n * Initial milliseconds between connection attempts\n * @type {Number}\n */\n backoffTimeReset: process.env.MERCURY_BACKOFF_TIME_RESET || 1000,\n /**\n * Milliseconds to wait for a close frame before declaring the socket dead and\n * discarding it\n * @type {[type]}\n */\n forceCloseDelay: process.env.MERCURY_FORCE_CLOSE_DELAY || 2000,\n },\n};\n\n/**\n * LLMChannel to provide socket connections\n */\nexport default class LLMChannel extends (Mercury as any) implements ILLMChannel {\n namespace = LLM;\n defaultSessionId = LLM_DEFAULT_SESSION;\n /**\n * Map to store connection-specific data for multiple LLM connections\n * @private\n * @type {Map<string, {webSocketUrl?: string; binding?: string; locusUrl?: string; datachannelUrl?: string}>}\n */\n private connections: Map<\n string,\n {\n webSocketUrl?: string;\n binding?: string;\n locusUrl?: string;\n datachannelUrl?: string;\n datachannelToken?: string;\n }\n > = new Map();\n\n private datachannelTokens: Record<DataChannelTokenType, string> = {\n [DataChannelTokenType.Default]: undefined,\n [DataChannelTokenType.PracticeSession]: undefined,\n };\n\n private refreshHandler?: () => Promise<{\n body: {datachannelToken: string; datachannelTokenType: DataChannelTokenType};\n }>;\n\n /**\n * Register to the websocket\n * @param {string} llmSocketUrl\n * @param {string} datachannelToken\n * @param {string} sessionId - Connection identifier\n * @returns {Promise<void>}\n */\n private register = async (\n llmSocketUrl: string,\n datachannelToken?: string,\n sessionId: string = LLM_DEFAULT_SESSION\n ): Promise<void> => {\n const isDataChannelTokenEnabled = await this.isDataChannelTokenEnabled();\n\n return this.request({\n method: 'POST',\n url: llmSocketUrl,\n body: {deviceUrl: this.webex.internal.device.url},\n headers:\n isDataChannelTokenEnabled && datachannelToken\n ? {'Data-Channel-Auth-Token': datachannelToken}\n : {},\n })\n .then((res: {body: {webSocketUrl: string; binding: string}}) => {\n // Get or create connection data\n const sessionData = this.connections.get(sessionId) || {};\n sessionData.webSocketUrl = res.body.webSocketUrl;\n sessionData.binding = res.body.binding;\n this.connections.set(sessionId, sessionData);\n })\n .catch((error: any) => {\n this.logger.error(`Error connecting to websocket for ${sessionId}: ${error}`);\n throw error;\n });\n };\n\n /**\n * Register and connect to the websocket\n * @param {string} locusUrl\n * @param {string} datachannelUrl\n * @param {string} datachannelToken\n * @param {string} sessionId - Connection identifier\n * @returns {Promise<void>}\n */\n public registerAndConnect = (\n locusUrl: string,\n datachannelUrl: string,\n datachannelToken?: string,\n sessionId: string = LLM_DEFAULT_SESSION\n ): Promise<void> =>\n this.register(datachannelUrl, datachannelToken, sessionId).then(() => {\n if (!locusUrl || !datachannelUrl) return undefined;\n\n // Get or create connection data\n const sessionData = this.connections.get(sessionId) || {};\n sessionData.locusUrl = locusUrl;\n sessionData.datachannelUrl = datachannelUrl;\n sessionData.datachannelToken = datachannelToken;\n this.connections.set(sessionId, sessionData);\n\n return this.connect(sessionData.webSocketUrl, sessionId);\n });\n\n /**\n * Tells if LLM socket is connected\n * @param {string} sessionId - Connection identifier\n * @returns {boolean} connected\n */\n public isConnected = (sessionId = LLM_DEFAULT_SESSION): boolean => {\n const socket = this.getSocket(sessionId);\n\n return socket ? socket.connected : false;\n };\n\n /**\n * Tells if LLM socket is binding\n * @param {string} sessionId - Connection identifier\n * @returns {string} binding\n */\n public getBinding = (sessionId = LLM_DEFAULT_SESSION): string => {\n const sessionData = this.connections.get(sessionId);\n\n return sessionData?.binding;\n };\n\n /**\n * Get Locus URL for the connection\n * @param {string} sessionId - Connection identifier\n * @returns {string} locus Url\n */\n public getLocusUrl = (sessionId = LLM_DEFAULT_SESSION): string => {\n const sessionData = this.connections.get(sessionId);\n\n return sessionData?.locusUrl;\n };\n\n /**\n * Get data channel URL for the connection\n * @param {string} sessionId - Connection identifier\n * @returns {string} data channel Url\n */\n public getDatachannelUrl = (sessionId = LLM_DEFAULT_SESSION): string => {\n const sessionData = this.connections.get(sessionId);\n\n return sessionData?.datachannelUrl;\n };\n\n /**\n * Get data channel token for the connection\n * @param {DataChannelTokenType} dataChannelTokenType\n * @returns {string} data channel token\n */\n public getDatachannelToken = (dataChannelTokenType: DataChannelTokenType): string => {\n return this.datachannelTokens[dataChannelTokenType];\n };\n\n /**\n * Set data channel token for the connection\n * @param {string} datachannelToken - data channel token\n * @param {DataChannelTokenType} dataChannelTokenType\n * @returns {void}\n */\n public setDatachannelToken = (\n datachannelToken: string,\n dataChannelTokenType: DataChannelTokenType\n ): void => {\n this.datachannelTokens[dataChannelTokenType] = datachannelToken;\n };\n\n /**\n * Set the handler used to refresh the DataChannel token\n *\n * @param {function} handler - Function that returns a refreshed token\n * @returns {void}\n */\n public setRefreshHandler(\n handler: () => Promise<{\n body: {datachannelToken: string; datachannelTokenType: DataChannelTokenType};\n }>\n ) {\n this.refreshHandler = handler;\n }\n\n /**\n * Refresh the data channel token using the injected handler.\n * Logs a descriptive error if the handler is missing or fails.\n *\n * @returns {Promise<string>} The refreshed token.\n */\n public async refreshDataChannelToken() {\n if (!this.refreshHandler) {\n const error = new Error('LLM refreshHandler is not set');\n this.logger.error(`Error refreshing DataChannel token: ${error.message}`);\n throw error;\n }\n\n try {\n const res = await this.refreshHandler();\n\n return res;\n } catch (error: any) {\n this.logger.error(`Error refreshing DataChannel token: ${error}`);\n throw error;\n }\n }\n\n /**\n * Disconnects websocket connection\n * @param {{code: number, reason: string}} options - The disconnect option object with code and reason\n * @param {string} sessionId - Connection identifier\n * @returns {Promise<void>}\n */\n public disconnectLLM = (\n options: {code: number; reason: string},\n sessionId: string = LLM_DEFAULT_SESSION\n ): Promise<void> =>\n this.disconnect(options, sessionId).then(() => {\n // Clean up sessions data\n this.connections.delete(sessionId);\n });\n\n /**\n * Disconnects all LLM websocket connections\n * @param {{code: number, reason: string}} options - The disconnect option object with code and reason\n * @returns {Promise<void>}\n */\n public disconnectAllLLM = (options?: {code: number; reason: string}): Promise<void> =>\n this.disconnectAll(options).then(() => {\n // Clean up all connection data\n this.connections.clear();\n });\n\n /**\n * Get all active LLM connections\n * @returns {Map} Map of sessionId to session data\n */\n public getAllConnections = (): Map<\n string,\n {\n webSocketUrl?: string;\n binding?: string;\n locusUrl?: string;\n datachannelUrl?: string;\n datachannelToken?: string;\n }\n > => new Map(this.connections);\n\n /**\n * Returns true if data channel token is enabled, false otherwise\n * @returns {Promise<boolean>} resolves with true if data channel token is enabled\n */\n public isDataChannelTokenEnabled(): Promise<boolean> {\n // @ts-ignore\n return this.webex.internal.feature.getFeature('developer', DATA_CHANNEL_WITH_JWT_TOKEN);\n }\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["_internalPluginMercury","_interopRequireDefault","require","_constants","_llm","_callSuper","t","o","e","_getPrototypeOf2","default","_possibleConstructorReturn2","_isNativeReflectConstruct","_Reflect$construct","constructor","apply","Boolean","prototype","valueOf","call","config","exports","llm","pingInterval","process","env","MERCURY_PING_INTERVAL","pongTimeout","MERCURY_PONG_TIMEOUT","backoffTimeMax","MERCURY_BACKOFF_TIME_MAX","backoffTimeReset","MERCURY_BACKOFF_TIME_RESET","forceCloseDelay","MERCURY_FORCE_CLOSE_DELAY","LLMChannel","_ref","_this","_classCallCheck2","_len","arguments","length","args","Array","_key","concat","_defineProperty3","LLM","LLM_DEFAULT_SESSION","_map","DataChannelTokenType","Default","undefined","PracticeSession","_ref2","_asyncToGenerator2","_regenerator","mark","_callee","llmSocketUrl","datachannelToken","sessionId","isDataChannelTokenEnabled","_args","wrap","_context","prev","next","sent","abrupt","request","method","url","body","deviceUrl","webex","internal","device","headers","then","res","sessionData","connections","get","webSocketUrl","binding","set","catch","error","logger","stop","_x","_x2","locusUrl","datachannelUrl","register","_callee2","connectUrl","_context2","buildUrlWithAwareSubchannels","AWARE_DATA_CHANNEL","connect","socket","getSocket","connected","dataChannelTokenType","datachannelTokens","options","disconnect","delete","disconnectAll","clear","resetDatachannelTokens","_inherits2","_createClass2","key","value","setRefreshHandler","handler","refreshHandler","_refreshDataChannelToken","_callee3","_t","_context3","warn","message","refreshDataChannelToken","feature","getFeature","DATA_CHANNEL_WITH_JWT_TOKEN","Mercury","baseUrl","subchannels","urlObj","URL","searchParams","SUBSCRIPTION_AWARE_SUBCHANNELS_PARAM","join","toString"],"sources":["llm.ts"],"sourcesContent":["/* eslint-disable consistent-return */\n\nimport Mercury from '@webex/internal-plugin-mercury';\n\n// eslint-disable-next-line no-unused-vars\nimport {\n LLM,\n DATA_CHANNEL_WITH_JWT_TOKEN,\n AWARE_DATA_CHANNEL,\n SUBSCRIPTION_AWARE_SUBCHANNELS_PARAM,\n LLM_DEFAULT_SESSION,\n} from './constants';\nimport {ILLMChannel, DataChannelTokenType} from './llm.types';\n\nexport const config = {\n llm: {\n /**\n * Milliseconds between pings sent up the socket\n * @type {number}\n */\n pingInterval: process.env.MERCURY_PING_INTERVAL || 15000,\n /**\n * Milliseconds to wait for a pong before declaring the connection dead\n * @type {number}\n */\n pongTimeout: process.env.MERCURY_PONG_TIMEOUT || 14000,\n /**\n * Maximum milliseconds between connection attempts\n * @type {Number}\n */\n backoffTimeMax: process.env.MERCURY_BACKOFF_TIME_MAX || 32000,\n /**\n * Initial milliseconds between connection attempts\n * @type {Number}\n */\n backoffTimeReset: process.env.MERCURY_BACKOFF_TIME_RESET || 1000,\n /**\n * Milliseconds to wait for a close frame before declaring the socket dead and\n * discarding it\n * @type {[type]}\n */\n forceCloseDelay: process.env.MERCURY_FORCE_CLOSE_DELAY || 2000,\n },\n};\n\n/**\n * LLMChannel to provide socket connections\n */\nexport default class LLMChannel extends (Mercury as any) implements ILLMChannel {\n namespace = LLM;\n defaultSessionId = LLM_DEFAULT_SESSION;\n /**\n * Map to store connection-specific data for multiple LLM connections\n * @private\n * @type {Map<string, {webSocketUrl?: string; binding?: string; locusUrl?: string; datachannelUrl?: string}>}\n */\n private connections: Map<\n string,\n {\n webSocketUrl?: string;\n binding?: string;\n locusUrl?: string;\n datachannelUrl?: string;\n datachannelToken?: string;\n }\n > = new Map();\n\n private datachannelTokens: Record<DataChannelTokenType, string> = {\n [DataChannelTokenType.Default]: undefined,\n [DataChannelTokenType.PracticeSession]: undefined,\n };\n\n private refreshHandler?: () => Promise<{\n body: {datachannelToken: string; datachannelTokenType: DataChannelTokenType};\n }>;\n\n /**\n * Register to the websocket\n * @param {string} llmSocketUrl\n * @param {string} datachannelToken\n * @param {string} sessionId - Connection identifier\n * @returns {Promise<void>}\n */\n private register = async (\n llmSocketUrl: string,\n datachannelToken?: string,\n sessionId: string = LLM_DEFAULT_SESSION\n ): Promise<void> => {\n const isDataChannelTokenEnabled = await this.isDataChannelTokenEnabled();\n\n return this.request({\n method: 'POST',\n url: llmSocketUrl,\n body: {deviceUrl: this.webex.internal.device.url},\n headers:\n isDataChannelTokenEnabled && datachannelToken\n ? {'Data-Channel-Auth-Token': datachannelToken}\n : {},\n })\n .then((res: {body: {webSocketUrl: string; binding: string}}) => {\n // Get or create connection data\n const sessionData = this.connections.get(sessionId) || {};\n sessionData.webSocketUrl = res.body.webSocketUrl;\n sessionData.binding = res.body.binding;\n this.connections.set(sessionId, sessionData);\n })\n .catch((error: any) => {\n this.logger.error(`Error connecting to websocket for ${sessionId}: ${error}`);\n throw error;\n });\n };\n\n /**\n * Register and connect to the websocket\n * @param {string} locusUrl\n * @param {string} datachannelUrl\n * @param {string} datachannelToken\n * @param {string} sessionId - Connection identifier\n * @returns {Promise<void>}\n */\n public registerAndConnect = (\n locusUrl: string,\n datachannelUrl: string,\n datachannelToken?: string,\n sessionId: string = LLM_DEFAULT_SESSION\n ): Promise<void> =>\n this.register(datachannelUrl, datachannelToken, sessionId).then(async () => {\n if (!locusUrl || !datachannelUrl) return undefined;\n\n // Get or create connection data\n const sessionData = this.connections.get(sessionId) || {};\n sessionData.locusUrl = locusUrl;\n sessionData.datachannelUrl = datachannelUrl;\n sessionData.datachannelToken = datachannelToken;\n this.connections.set(sessionId, sessionData);\n\n const isDataChannelTokenEnabled = await this.isDataChannelTokenEnabled();\n\n const connectUrl = isDataChannelTokenEnabled\n ? LLMChannel.buildUrlWithAwareSubchannels(sessionData.webSocketUrl, AWARE_DATA_CHANNEL)\n : sessionData.webSocketUrl;\n\n return this.connect(connectUrl, sessionId);\n });\n\n /**\n * Tells if LLM socket is connected\n * @param {string} sessionId - Connection identifier\n * @returns {boolean} connected\n */\n public isConnected = (sessionId = LLM_DEFAULT_SESSION): boolean => {\n const socket = this.getSocket(sessionId);\n\n return socket ? socket.connected : false;\n };\n\n /**\n * Tells if LLM socket is binding\n * @param {string} sessionId - Connection identifier\n * @returns {string} binding\n */\n public getBinding = (sessionId = LLM_DEFAULT_SESSION): string => {\n const sessionData = this.connections.get(sessionId);\n\n return sessionData?.binding;\n };\n\n /**\n * Get Locus URL for the connection\n * @param {string} sessionId - Connection identifier\n * @returns {string} locus Url\n */\n public getLocusUrl = (sessionId = LLM_DEFAULT_SESSION): string => {\n const sessionData = this.connections.get(sessionId);\n\n return sessionData?.locusUrl;\n };\n\n /**\n * Get data channel URL for the connection\n * @param {string} sessionId - Connection identifier\n * @returns {string} data channel Url\n */\n public getDatachannelUrl = (sessionId = LLM_DEFAULT_SESSION): string => {\n const sessionData = this.connections.get(sessionId);\n\n return sessionData?.datachannelUrl;\n };\n\n /**\n * Get data channel token for the connection\n * @param {DataChannelTokenType} dataChannelTokenType\n * @returns {string} data channel token\n */\n public getDatachannelToken = (\n dataChannelTokenType: DataChannelTokenType = DataChannelTokenType.Default\n ): string => {\n return this.datachannelTokens[dataChannelTokenType];\n };\n\n /**\n * Set data channel token for the connection\n * @param {string} datachannelToken - data channel token\n * @param {DataChannelTokenType} dataChannelTokenType\n * @returns {void}\n */\n public setDatachannelToken = (\n datachannelToken: string,\n dataChannelTokenType: DataChannelTokenType = DataChannelTokenType.Default\n ): void => {\n this.datachannelTokens[dataChannelTokenType] = datachannelToken;\n };\n\n /**\n * Resets all data‑channel tokens to their initial undefined values.\n * Used when leaving or disconnecting from a meeting.\n * @returns {void}\n */\n private resetDatachannelTokens() {\n this.datachannelTokens = {\n [DataChannelTokenType.Default]: undefined,\n [DataChannelTokenType.PracticeSession]: undefined,\n };\n }\n\n /**\n * Set the handler used to refresh the DataChannel token\n *\n * @param {function} handler - Function that returns a refreshed token\n * @returns {void}\n */\n public setRefreshHandler(\n handler: () => Promise<{\n body: {datachannelToken: string; datachannelTokenType: DataChannelTokenType};\n }>\n ) {\n this.refreshHandler = handler;\n }\n\n /**\n * Refresh the data channel token using the injected handler.\n * Logs a descriptive error if the handler is missing or fails.\n *\n * @returns {Promise<string>} The refreshed token.\n */\n public async refreshDataChannelToken() {\n if (!this.refreshHandler) {\n this.logger.warn(\n 'llm#refreshDataChannelToken --> LLM refreshHandler is not set, skipping token refresh'\n );\n\n return null;\n }\n\n try {\n const res = await this.refreshHandler();\n\n return res;\n } catch (error: any) {\n this.logger.warn(\n `llm#refreshDataChannelToken --> DataChannel token refresh failed (likely locus changed or participant left): ${\n error?.message || error\n }`\n );\n\n return null;\n }\n }\n\n /**\n * Disconnects websocket connection\n * @param {{code: number, reason: string}} options - The disconnect option object with code and reason\n * @param {string} sessionId - Connection identifier\n * @returns {Promise<void>}\n */\n public disconnectLLM = (\n options: {code: number; reason: string},\n sessionId: string = LLM_DEFAULT_SESSION\n ): Promise<void> =>\n this.disconnect(options, sessionId).then(() => {\n // Clean up sessions data\n this.connections.delete(sessionId);\n this.datachannelTokens[sessionId] = undefined;\n });\n\n /**\n * Disconnects all LLM websocket connections\n * @param {{code: number, reason: string}} options - The disconnect option object with code and reason\n * @returns {Promise<void>}\n */\n public disconnectAllLLM = (options?: {code: number; reason: string}): Promise<void> =>\n this.disconnectAll(options).then(() => {\n // Clean up all connection data\n this.connections.clear();\n this.resetDatachannelTokens();\n });\n\n /**\n * Get all active LLM connections\n * @returns {Map} Map of sessionId to session data\n */\n public getAllConnections = (): Map<\n string,\n {\n webSocketUrl?: string;\n binding?: string;\n locusUrl?: string;\n datachannelUrl?: string;\n datachannelToken?: string;\n }\n > => new Map(this.connections);\n\n /**\n * Returns true if data channel token is enabled, false otherwise\n * @returns {Promise<boolean>} resolves with true if data channel token is enabled\n */\n public isDataChannelTokenEnabled(): Promise<boolean> {\n // @ts-ignore\n return this.webex.internal.feature.getFeature('developer', DATA_CHANNEL_WITH_JWT_TOKEN);\n }\n\n /**\n * Builds a WebSocket URL with the `subscriptionAwareSubchannels` query parameter.\n *\n * @param {string} baseUrl - The original WebSocket URL.\n * @param {string[]} subchannels - List of subchannels to declare as subscription-aware.\n * @returns {string} The final URL with updated query parameters.\n */\n\n public static buildUrlWithAwareSubchannels = (baseUrl: string, subchannels: string[]) => {\n const urlObj = new URL(baseUrl);\n urlObj.searchParams.set(SUBSCRIPTION_AWARE_SUBCHANNELS_PARAM, subchannels.join(','));\n\n return urlObj.toString();\n };\n}\n"],"mappings":"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","ignoreList":[]}
package/dist/llm.types.js CHANGED
@@ -6,8 +6,8 @@ _Object$defineProperty(exports, "__esModule", {
6
6
  });
7
7
  exports.DataChannelTokenType = void 0;
8
8
  var DataChannelTokenType = exports.DataChannelTokenType = /*#__PURE__*/function (DataChannelTokenType) {
9
- DataChannelTokenType["Default"] = "default";
10
- DataChannelTokenType["PracticeSession"] = "practiceSession";
9
+ DataChannelTokenType["Default"] = "llm-default-session";
10
+ DataChannelTokenType["PracticeSession"] = "llm-practice-session";
11
11
  return DataChannelTokenType;
12
12
  }({}); // eslint-disable-next-line import/prefer-default-export
13
13
  //# sourceMappingURL=llm.types.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["DataChannelTokenType","exports"],"sources":["llm.types.ts"],"sourcesContent":["interface ILLMChannel {\n registerAndConnect: (\n locusUrl: string,\n datachannelUrl: string,\n datachannelToken?: string,\n sessionId?: string\n ) => Promise<void>;\n isConnected: (sessionId?: string) => boolean;\n getBinding: (sessionId?: string) => string;\n getLocusUrl: (sessionId?: string) => string;\n getDatachannelUrl: (sessionId?: string) => string;\n disconnectLLM: (options: {code: number; reason: string}, sessionId?: string) => Promise<void>;\n disconnectAllLLM: (options?: {code: number; reason: string}) => Promise<void>;\n getAllConnections: () => Map<\n string,\n {\n webSocketUrl?: string;\n binding?: string;\n locusUrl?: string;\n datachannelUrl?: string;\n datachannelToken?: string;\n }\n >;\n}\n\nexport enum DataChannelTokenType {\n Default = 'default',\n PracticeSession = 'practiceSession',\n}\n\n// eslint-disable-next-line import/prefer-default-export\nexport type {ILLMChannel};\n"],"mappings":";;;;;;;IAyBYA,oBAAoB,GAAAC,OAAA,CAAAD,oBAAA,0BAApBA,oBAAoB;EAApBA,oBAAoB;EAApBA,oBAAoB;EAAA,OAApBA,oBAAoB;AAAA,OAKhC","ignoreList":[]}
1
+ {"version":3,"names":["DataChannelTokenType","exports"],"sources":["llm.types.ts"],"sourcesContent":["interface ILLMChannel {\n registerAndConnect: (\n locusUrl: string,\n datachannelUrl: string,\n datachannelToken?: string,\n sessionId?: string\n ) => Promise<void>;\n isConnected: (sessionId?: string) => boolean;\n getBinding: (sessionId?: string) => string;\n getLocusUrl: (sessionId?: string) => string;\n getDatachannelUrl: (sessionId?: string) => string;\n disconnectLLM: (options: {code: number; reason: string}, sessionId?: string) => Promise<void>;\n disconnectAllLLM: (options?: {code: number; reason: string}) => Promise<void>;\n getAllConnections: () => Map<\n string,\n {\n webSocketUrl?: string;\n binding?: string;\n locusUrl?: string;\n datachannelUrl?: string;\n datachannelToken?: string;\n }\n >;\n}\n\nexport enum DataChannelTokenType {\n Default = 'llm-default-session',\n PracticeSession = 'llm-practice-session',\n}\n\n// eslint-disable-next-line import/prefer-default-export\nexport type {ILLMChannel};\n"],"mappings":";;;;;;;IAyBYA,oBAAoB,GAAAC,OAAA,CAAAD,oBAAA,0BAApBA,oBAAoB;EAApBA,oBAAoB;EAApBA,oBAAoB;EAAA,OAApBA,oBAAoB;AAAA,OAKhC","ignoreList":[]}
package/package.json CHANGED
@@ -13,7 +13,7 @@
13
13
  "node": ">=18"
14
14
  },
15
15
  "dependencies": {
16
- "@webex/internal-plugin-mercury": "3.11.0-next.9"
16
+ "@webex/internal-plugin-mercury": "3.11.0-next.10"
17
17
  },
18
18
  "browserify": {
19
19
  "transform": [
@@ -44,5 +44,5 @@
44
44
  "test:style": "eslint ./src/**/*.*",
45
45
  "test:unit": "webex-legacy-tools test --unit --runner jest"
46
46
  },
47
- "version": "3.11.0-next.10"
47
+ "version": "3.11.0-next.12"
48
48
  }
package/src/constants.ts CHANGED
@@ -4,3 +4,11 @@ export const LLM = 'llm';
4
4
  export const LLM_DEFAULT_SESSION = 'llm-default-session';
5
5
 
6
6
  export const DATA_CHANNEL_WITH_JWT_TOKEN = 'data-channel-with-jwt-token';
7
+
8
+ export const SUBSCRIPTION_AWARE_SUBCHANNELS_PARAM = 'subscriptionAwareSubchannels';
9
+
10
+ export const DATA_CHNANEL_TYPE = {
11
+ TRANSCRIPTION: 'transcription',
12
+ };
13
+
14
+ export const AWARE_DATA_CHANNEL = [DATA_CHNANEL_TYPE.TRANSCRIPTION];
package/src/llm.ts CHANGED
@@ -2,8 +2,14 @@
2
2
 
3
3
  import Mercury from '@webex/internal-plugin-mercury';
4
4
 
5
- import {LLM, DATA_CHANNEL_WITH_JWT_TOKEN, LLM_DEFAULT_SESSION} from './constants';
6
5
  // eslint-disable-next-line no-unused-vars
6
+ import {
7
+ LLM,
8
+ DATA_CHANNEL_WITH_JWT_TOKEN,
9
+ AWARE_DATA_CHANNEL,
10
+ SUBSCRIPTION_AWARE_SUBCHANNELS_PARAM,
11
+ LLM_DEFAULT_SESSION,
12
+ } from './constants';
7
13
  import {ILLMChannel, DataChannelTokenType} from './llm.types';
8
14
 
9
15
  export const config = {
@@ -118,7 +124,7 @@ export default class LLMChannel extends (Mercury as any) implements ILLMChannel
118
124
  datachannelToken?: string,
119
125
  sessionId: string = LLM_DEFAULT_SESSION
120
126
  ): Promise<void> =>
121
- this.register(datachannelUrl, datachannelToken, sessionId).then(() => {
127
+ this.register(datachannelUrl, datachannelToken, sessionId).then(async () => {
122
128
  if (!locusUrl || !datachannelUrl) return undefined;
123
129
 
124
130
  // Get or create connection data
@@ -128,7 +134,13 @@ export default class LLMChannel extends (Mercury as any) implements ILLMChannel
128
134
  sessionData.datachannelToken = datachannelToken;
129
135
  this.connections.set(sessionId, sessionData);
130
136
 
131
- return this.connect(sessionData.webSocketUrl, sessionId);
137
+ const isDataChannelTokenEnabled = await this.isDataChannelTokenEnabled();
138
+
139
+ const connectUrl = isDataChannelTokenEnabled
140
+ ? LLMChannel.buildUrlWithAwareSubchannels(sessionData.webSocketUrl, AWARE_DATA_CHANNEL)
141
+ : sessionData.webSocketUrl;
142
+
143
+ return this.connect(connectUrl, sessionId);
132
144
  });
133
145
 
134
146
  /**
@@ -180,7 +192,9 @@ export default class LLMChannel extends (Mercury as any) implements ILLMChannel
180
192
  * @param {DataChannelTokenType} dataChannelTokenType
181
193
  * @returns {string} data channel token
182
194
  */
183
- public getDatachannelToken = (dataChannelTokenType: DataChannelTokenType): string => {
195
+ public getDatachannelToken = (
196
+ dataChannelTokenType: DataChannelTokenType = DataChannelTokenType.Default
197
+ ): string => {
184
198
  return this.datachannelTokens[dataChannelTokenType];
185
199
  };
186
200
 
@@ -192,11 +206,23 @@ export default class LLMChannel extends (Mercury as any) implements ILLMChannel
192
206
  */
193
207
  public setDatachannelToken = (
194
208
  datachannelToken: string,
195
- dataChannelTokenType: DataChannelTokenType
209
+ dataChannelTokenType: DataChannelTokenType = DataChannelTokenType.Default
196
210
  ): void => {
197
211
  this.datachannelTokens[dataChannelTokenType] = datachannelToken;
198
212
  };
199
213
 
214
+ /**
215
+ * Resets all data‑channel tokens to their initial undefined values.
216
+ * Used when leaving or disconnecting from a meeting.
217
+ * @returns {void}
218
+ */
219
+ private resetDatachannelTokens() {
220
+ this.datachannelTokens = {
221
+ [DataChannelTokenType.Default]: undefined,
222
+ [DataChannelTokenType.PracticeSession]: undefined,
223
+ };
224
+ }
225
+
200
226
  /**
201
227
  * Set the handler used to refresh the DataChannel token
202
228
  *
@@ -219,9 +245,11 @@ export default class LLMChannel extends (Mercury as any) implements ILLMChannel
219
245
  */
220
246
  public async refreshDataChannelToken() {
221
247
  if (!this.refreshHandler) {
222
- const error = new Error('LLM refreshHandler is not set');
223
- this.logger.error(`Error refreshing DataChannel token: ${error.message}`);
224
- throw error;
248
+ this.logger.warn(
249
+ 'llm#refreshDataChannelToken --> LLM refreshHandler is not set, skipping token refresh'
250
+ );
251
+
252
+ return null;
225
253
  }
226
254
 
227
255
  try {
@@ -229,8 +257,13 @@ export default class LLMChannel extends (Mercury as any) implements ILLMChannel
229
257
 
230
258
  return res;
231
259
  } catch (error: any) {
232
- this.logger.error(`Error refreshing DataChannel token: ${error}`);
233
- throw error;
260
+ this.logger.warn(
261
+ `llm#refreshDataChannelToken --> DataChannel token refresh failed (likely locus changed or participant left): ${
262
+ error?.message || error
263
+ }`
264
+ );
265
+
266
+ return null;
234
267
  }
235
268
  }
236
269
 
@@ -247,6 +280,7 @@ export default class LLMChannel extends (Mercury as any) implements ILLMChannel
247
280
  this.disconnect(options, sessionId).then(() => {
248
281
  // Clean up sessions data
249
282
  this.connections.delete(sessionId);
283
+ this.datachannelTokens[sessionId] = undefined;
250
284
  });
251
285
 
252
286
  /**
@@ -258,6 +292,7 @@ export default class LLMChannel extends (Mercury as any) implements ILLMChannel
258
292
  this.disconnectAll(options).then(() => {
259
293
  // Clean up all connection data
260
294
  this.connections.clear();
295
+ this.resetDatachannelTokens();
261
296
  });
262
297
 
263
298
  /**
@@ -283,4 +318,19 @@ export default class LLMChannel extends (Mercury as any) implements ILLMChannel
283
318
  // @ts-ignore
284
319
  return this.webex.internal.feature.getFeature('developer', DATA_CHANNEL_WITH_JWT_TOKEN);
285
320
  }
321
+
322
+ /**
323
+ * Builds a WebSocket URL with the `subscriptionAwareSubchannels` query parameter.
324
+ *
325
+ * @param {string} baseUrl - The original WebSocket URL.
326
+ * @param {string[]} subchannels - List of subchannels to declare as subscription-aware.
327
+ * @returns {string} The final URL with updated query parameters.
328
+ */
329
+
330
+ public static buildUrlWithAwareSubchannels = (baseUrl: string, subchannels: string[]) => {
331
+ const urlObj = new URL(baseUrl);
332
+ urlObj.searchParams.set(SUBSCRIPTION_AWARE_SUBCHANNELS_PARAM, subchannels.join(','));
333
+
334
+ return urlObj.toString();
335
+ };
286
336
  }
package/src/llm.types.ts CHANGED
@@ -24,8 +24,8 @@ interface ILLMChannel {
24
24
  }
25
25
 
26
26
  export enum DataChannelTokenType {
27
- Default = 'default',
28
- PracticeSession = 'practiceSession',
27
+ Default = 'llm-default-session',
28
+ PracticeSession = 'llm-practice-session',
29
29
  }
30
30
 
31
31
  // eslint-disable-next-line import/prefer-default-export
@@ -25,55 +25,77 @@ describe('plugin-llm', () => {
25
25
  };
26
26
 
27
27
  llmService = webex.internal.llm;
28
- llmService.connect = sinon.stub().callsFake(() => {
29
- // Simulate a successful connection by stubbing getSocket to return connected: true
30
- llmService.getSocket = sinon.stub().returns({connected: true});
31
- });
28
+ llmService.webSocketUrl = 'wss://example.com/socket';
32
29
  llmService.disconnect = sinon.stub().resolves(true);
33
30
  llmService.request = sinon.stub().resolves({
34
31
  headers: {},
35
32
  body: {
36
33
  binding: 'binding',
37
- webSocketUrl: 'url',
34
+ webSocketUrl: 'wss://example.com/socket',
38
35
  },
39
36
  });
37
+ const sockets = new Map();
38
+
39
+ llmService.connect = sinon.stub().callsFake((url, sessionId) => {
40
+ sockets.set(sessionId, {connected: true});
41
+ llmService.getSocket = sinon.stub().callsFake((sid) => sockets.get(sid));
42
+ });
43
+ llmService.connections.set('llm-default-session',{
44
+ webSocketUrl: 'wss://example.com/socket',
45
+ })
40
46
  });
41
47
 
48
+ afterEach(() => sinon.restore());
49
+
42
50
  describe('#registerAndConnect', () => {
43
51
  it('registers connection', async () => {
44
- llmService.register = sinon.stub().resolves({
45
- body: {
46
- binding: 'binding',
47
- webSocketUrl: 'url',
48
- },
52
+ llmService.register = sinon.stub().callsFake(async () => {
53
+ llmService.binding = 'binding';
54
+ llmService.webSocketUrl = 'wss://example.com/socket';
55
+ return {
56
+ body: {
57
+ binding: 'binding',
58
+ webSocketUrl: 'wss://example.com/socket',
59
+ },
60
+ };
49
61
  });
50
- assert.equal(llmService.isConnected(), false);
51
- await llmService.registerAndConnect(locusUrl, datachannelUrl);
52
- assert.equal(llmService.isConnected(), true);
62
+
63
+ assert.equal(llmService.isConnected('llm-default-session'), false);
64
+ await llmService.registerAndConnect(locusUrl, datachannelUrl,undefined);
65
+ assert.equal(llmService.isConnected('llm-default-session'), true);
53
66
  });
54
67
 
55
- it("doesn't registers connection for invalid input", async () => {
56
- llmService.register = sinon.stub().resolves({
57
- body: {
58
- binding: 'binding',
59
- webSocketUrl: 'url',
60
- },
68
+ it("doesn't register connection for invalid input", async () => {
69
+ llmService.register = sinon.stub().callsFake(async () => {
70
+ llmService.binding = 'binding';
71
+ llmService.webSocketUrl = 'wss://example.com/socket';
72
+ return {
73
+ body: {
74
+ binding: 'binding',
75
+ webSocketUrl: 'wss://example.com/socket',
76
+ },
77
+ };
61
78
  });
79
+
62
80
  await llmService.registerAndConnect();
63
81
  assert.equal(llmService.isConnected(), false);
64
82
  });
65
83
 
66
84
  it('registers connection with token', async () => {
67
- llmService.register = sinon.stub().resolves({
68
- body: {
69
- binding: 'binding',
70
- webSocketUrl: 'url',
71
- },
85
+ llmService.register = sinon.stub().callsFake(async () => {
86
+ llmService.binding = 'binding';
87
+ llmService.webSocketUrl = 'wss://example.com/socket';
88
+ return {
89
+ body: {
90
+ binding: 'binding',
91
+ webSocketUrl: 'wss://example.com/socket',
92
+ },
93
+ };
72
94
  });
73
95
 
74
96
  assert.equal(llmService.isConnected(), false);
75
97
 
76
- await llmService.registerAndConnect(locusUrl, datachannelUrl, 'abc123');
98
+ await llmService.registerAndConnect(locusUrl, datachannelUrl,'abc123');
77
99
 
78
100
  sinon.assert.calledOnceWithExactly(
79
101
  llmService.register,
@@ -84,6 +106,72 @@ describe('plugin-llm', () => {
84
106
 
85
107
  assert.equal(llmService.isConnected(), true);
86
108
  });
109
+
110
+ it('connects with subscriptionAwareSubchannels when token enabled', async () => {
111
+ llmService.isDataChannelTokenEnabled = sinon.stub().returns(true);
112
+
113
+ llmService.register = sinon.stub().callsFake(async () => {
114
+ llmService.binding = 'binding';
115
+ llmService.webSocketUrl = 'wss://example.com/socket';
116
+ return {
117
+ body: {
118
+ binding: 'binding',
119
+ webSocketUrl: 'wss://example.com/socket',
120
+ },
121
+ };
122
+ });
123
+
124
+ const buildSpy = sinon.spy(LLMService, 'buildUrlWithAwareSubchannels');
125
+
126
+ await llmService.registerAndConnect(locusUrl, datachannelUrl,'abc123');
127
+
128
+ sinon.assert.calledOnce(buildSpy);
129
+ sinon.assert.calledOnce(llmService.connect);
130
+
131
+ const calledUrl = llmService.connect.getCall(0).args[0];
132
+ assert.include(calledUrl, 'subscriptionAwareSubchannels=');
133
+ });
134
+
135
+ it('connects without subscriptionAwareSubchannels when token disabled', async () => {
136
+ llmService.isDataChannelTokenEnabled = sinon.stub().returns(false);
137
+
138
+ llmService.register = sinon.stub().callsFake(async () => {
139
+ llmService.binding = 'binding';
140
+ llmService.webSocketUrl = 'wss://example.com/socket';
141
+ return {
142
+ body: {
143
+ binding: 'binding',
144
+ webSocketUrl: 'wss://example.com/socket',
145
+ },
146
+ };
147
+ });
148
+
149
+ const buildSpy = sinon.spy(LLMService, 'buildUrlWithAwareSubchannels');
150
+
151
+ await llmService.registerAndConnect(locusUrl, datachannelUrl);
152
+
153
+ sinon.assert.notCalled(buildSpy);
154
+ sinon.assert.calledOnce(llmService.connect);
155
+
156
+ const calledUrl = llmService.connect.getCall(0).args[0];
157
+ assert.equal(calledUrl, llmService.webSocketUrl);
158
+ });
159
+
160
+ it('connects without subscriptionAwareSubchannels when token enabled BUT token missing', async () => {
161
+ llmService.isDataChannelTokenEnabled = sinon.stub().resolves(true);
162
+
163
+ const buildSpy = sinon.spy(LLMService, 'buildUrlWithAwareSubchannels');
164
+
165
+ await llmService.registerAndConnect(locusUrl, datachannelUrl, undefined);
166
+
167
+ sinon.assert.calledOnce(buildSpy);
168
+ sinon.assert.calledOnce(llmService.connect);
169
+
170
+ const calledUrl = llmService.connect.getCall(0).args[0];
171
+ assert.include(calledUrl, 'subscriptionAwareSubchannels=');
172
+
173
+ buildSpy.restore();
174
+ });
87
175
  });
88
176
 
89
177
  describe('#register', () => {
@@ -136,15 +224,19 @@ describe('plugin-llm', () => {
136
224
  });
137
225
  });
138
226
 
139
-
140
227
  describe('#getLocusUrl', () => {
141
228
  it('gets LocusUrl', async () => {
142
- llmService.register = sinon.stub().resolves({
143
- body: {
144
- binding: 'binding',
145
- webSocketUrl: 'url',
146
- },
229
+ llmService.register = sinon.stub().callsFake(async () => {
230
+ llmService.binding = 'binding';
231
+ llmService.webSocketUrl = 'wss://example.com/socket';
232
+ return {
233
+ body: {
234
+ binding: 'binding',
235
+ webSocketUrl: 'wss://example.com/socket',
236
+ },
237
+ };
147
238
  });
239
+
148
240
  await llmService.registerAndConnect(locusUrl, datachannelUrl);
149
241
  assert.equal(llmService.getLocusUrl(), locusUrl);
150
242
  });
@@ -152,11 +244,15 @@ describe('plugin-llm', () => {
152
244
 
153
245
  describe('#getDatachannelUrl', () => {
154
246
  it('gets dataChannel Url', async () => {
155
- llmService.register = sinon.stub().resolves({
156
- body: {
157
- binding: 'binding',
158
- webSocketUrl: 'url',
159
- },
247
+ llmService.register = sinon.stub().callsFake(async () => {
248
+ llmService.binding = 'binding';
249
+ llmService.webSocketUrl = 'wss://example.com/socket';
250
+ return {
251
+ body: {
252
+ binding: 'binding',
253
+ webSocketUrl: 'wss://example.com/socket',
254
+ },
255
+ };
160
256
  });
161
257
  await llmService.registerAndConnect(locusUrl, datachannelUrl);
162
258
  assert.equal(llmService.getDatachannelUrl(), datachannelUrl);
@@ -174,41 +270,47 @@ describe('plugin-llm', () => {
174
270
  });
175
271
  });
176
272
 
177
- describe('disconnectLLM', () => {
273
+ describe('#disconnectLLM', () => {
178
274
  let instance;
179
275
 
180
276
  beforeEach(() => {
181
277
  instance = {
182
278
  disconnect: jest.fn(() => Promise.resolve()),
183
- locusUrl: 'someUrl',
184
- datachannelUrl: 'someUrl',
185
- binding: {},
186
- webSocketUrl: 'someUrl',
187
- disconnectLLM: function (options) {
188
- return this.disconnect(options).then(() => {
189
- this.locusUrl = undefined;
190
- this.datachannelUrl = undefined;
191
- this.binding = undefined;
192
- this.webSocketUrl = undefined;
279
+ connections: new Map([
280
+ ['llm-default-session', { foo: 'bar' }],
281
+ ]),
282
+ datachannelTokens: {
283
+ 'llm-default-session': 'session-token',
284
+ },
285
+
286
+ disconnectLLM: function (options, sessionId = 'llm-default-session') {
287
+ return this.disconnect(options, sessionId).then(() => {
288
+ this.connections.delete(sessionId);
289
+ this.datachannelTokens[sessionId] = undefined;
193
290
  });
194
- }
291
+ },
195
292
  };
196
293
  });
197
294
 
198
- it('should call disconnect and clear relevant properties', async () => {
199
- await instance.disconnectLLM({});
295
+ it('calls disconnect and clears session connection + token', async () => {
296
+ await instance.disconnectLLM({ code: 3000, reason: 'bye' });
297
+
298
+ expect(instance.disconnect).toHaveBeenCalledWith(
299
+ { code: 3000, reason: 'bye' },
300
+ 'llm-default-session'
301
+ );
302
+
303
+ expect(instance.connections.has('llm-default-session')).toBe(false);
200
304
 
201
- expect(instance.disconnect).toHaveBeenCalledWith({});
202
- expect(instance.locusUrl).toBeUndefined();
203
- expect(instance.datachannelUrl).toBeUndefined();
204
- expect(instance.binding).toBeUndefined();
205
- expect(instance.webSocketUrl).toBeUndefined();
305
+ expect(instance.datachannelTokens['llm-default-session']).toBeUndefined();
206
306
  });
207
307
 
208
- it('should handle errors from disconnect gracefully', async () => {
209
- instance.disconnect.mockRejectedValue(new Error('Disconnect failed'));
308
+ it('propagates disconnect errors', async () => {
309
+ instance.disconnect.mockRejectedValue(new Error('disconnect failed'));
210
310
 
211
- await expect(instance.disconnectLLM({})).rejects.toThrow('Disconnect failed');
311
+ await expect(
312
+ instance.disconnectLLM({ code: 3000, reason: 'bye' })
313
+ ).rejects.toThrow('disconnect failed');
212
314
  });
213
315
  });
214
316
 
@@ -239,53 +341,56 @@ describe('plugin-llm', () => {
239
341
  });
240
342
 
241
343
  describe('#refreshDataChannelToken', () => {
242
- it('throws if no handler is set', async () => {
243
- try {
244
- await llmService.refreshDataChannelToken();
245
- assert.fail('Should have thrown');
246
- } catch (err) {
247
- assert.match(err.message, 'LLM refreshHandler is not set');
248
- }
344
+ it('returns null and logs warn if no handler is set', async () => {
345
+ const warnSpy = llmService.logger.warn
346
+
347
+ const result = await llmService.refreshDataChannelToken();
348
+
349
+ assert.equal(result, null);
350
+
351
+ sinon.assert.calledOnce(warnSpy);
352
+ sinon.assert.calledWithMatch(
353
+ warnSpy,
354
+ sinon.match('LLM refreshHandler is not set')
355
+ );
249
356
  });
250
357
 
251
358
  it('returns token when handler resolves', async () => {
252
- const mockToken = { body: { datachannelToken: 'newToken' ,isPracticeSession: false} }
359
+ const mockToken = { body: { datachannelToken: 'newToken', isPracticeSession: false } };
253
360
  const handler = sinon.stub().resolves(mockToken);
361
+
254
362
  llmService.setRefreshHandler(handler);
255
363
 
256
364
  const token = await llmService.refreshDataChannelToken();
365
+
257
366
  assert.equal(token, mockToken);
258
367
  sinon.assert.calledOnce(handler);
259
368
  });
260
369
 
261
- it('logs and rethrows when handler rejects', async () => {
370
+ it('logs warn and returns null when handler rejects', async () => {
262
371
  const handler = sinon.stub().rejects(new Error('throw error'));
372
+ llmService.setRefreshHandler(handler);
263
373
 
264
- const loggerSpy = llmService.logger.error;
374
+ const warnSpy = llmService.logger.warn
265
375
 
266
- llmService.setRefreshHandler(handler);
376
+ const result = await llmService.refreshDataChannelToken();
267
377
 
268
- try {
269
- await llmService.refreshDataChannelToken();
270
- assert.fail('Should have thrown');
271
- } catch (err) {
272
- assert.match(err.message, /throw error/);
273
- }
378
+ assert.equal(result, null);
274
379
 
275
- sinon.assert.calledOnce(loggerSpy);
380
+ sinon.assert.calledOnce(warnSpy);
276
381
  sinon.assert.calledWithMatch(
277
- loggerSpy,
278
- sinon.match("Error refreshing DataChannel token: Error: throw error")
382
+ warnSpy,
383
+ sinon.match('DataChannel token refresh failed'),
279
384
  );
280
385
  });
281
386
  });
282
387
 
283
388
  describe('#getDatachannelToken / #setDatachannelToken', () => {
284
389
  it('sets and gets datachannel token', () => {
285
- llmService.setDatachannelToken('abc123','default');
286
- assert.equal(llmService.getDatachannelToken('default'), 'abc123');
287
- llmService.setDatachannelToken('123abc','practiceSession');
288
- assert.equal(llmService.getDatachannelToken('practiceSession'), '123abc');
390
+ llmService.setDatachannelToken('abc123','llm-default-session');
391
+ assert.equal(llmService.getDatachannelToken('llm-default-session'), 'abc123');
392
+ llmService.setDatachannelToken('123abc','llm-practice-session');
393
+ assert.equal(llmService.getDatachannelToken('llm-practice-session'), '123abc');
289
394
  });
290
395
  });
291
396
 
@@ -293,15 +398,6 @@ describe('plugin-llm', () => {
293
398
  const locusUrl2 = 'locusUrl2';
294
399
  const datachannelUrl2 = 'datachannelUrl2';
295
400
 
296
- beforeEach(() => {
297
- const sockets = new Map();
298
-
299
- llmService.connect = sinon.stub().callsFake((url, sessionId) => {
300
- sockets.set(sessionId, {connected: true});
301
- llmService.getSocket = sinon.stub().callsFake((sid) => sockets.get(sid));
302
- });
303
- });
304
-
305
401
  it('tracks multiple sessions independently', async () => {
306
402
  await llmService.registerAndConnect(locusUrl, datachannelUrl, undefined, 's1');
307
403
  await llmService.registerAndConnect(locusUrl2, datachannelUrl2, undefined, 's2');
@@ -314,7 +410,6 @@ describe('plugin-llm', () => {
314
410
  assert.equal(llmService.getDatachannelUrl('s2'), datachannelUrl2);
315
411
 
316
412
  const all = llmService.getAllConnections();
317
- assert.equal(all.size, 2);
318
413
  assert.equal(all.has('s1'), true);
319
414
  assert.equal(all.has('s2'), true);
320
415
  });
@@ -333,10 +428,13 @@ describe('plugin-llm', () => {
333
428
  const all = llmService.getAllConnections();
334
429
  assert.equal(all.has('s1'), false);
335
430
  assert.equal(all.has('s2'), true);
431
+
432
+ assert.equal(llmService.datachannelTokens['s1'], undefined);
336
433
  });
337
434
 
338
435
  it('disconnectAllLLM clears all sessions', async () => {
339
436
  llmService.disconnectAll = sinon.stub().resolves(true);
437
+ sinon.spy(llmService, 'resetDatachannelTokens');
340
438
 
341
439
  await llmService.registerAndConnect(locusUrl, datachannelUrl, undefined, 's1');
342
440
  await llmService.registerAndConnect(locusUrl2, datachannelUrl2, undefined, 's2');
@@ -345,7 +443,10 @@ describe('plugin-llm', () => {
345
443
 
346
444
  sinon.assert.calledOnce(llmService.disconnectAll);
347
445
  assert.equal(llmService.getAllConnections().size, 0);
446
+
447
+ sinon.assert.calledOnce(llmService.resetDatachannelTokens);
348
448
  });
349
449
  });
450
+
350
451
  });
351
452
  });