@webex/internal-plugin-llm 3.12.0-llmrefactor.4 → 3.12.0-llmrefactor.5

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/dist/llm.js CHANGED
@@ -81,7 +81,7 @@ var LLMChannel = exports.default = /*#__PURE__*/function (_ref) {
81
81
  * Register to the websocket
82
82
  * @param {string} llmSocketUrl
83
83
  * @param {string} datachannelToken
84
- * @returns {Promise<void>}
84
+ * @returns {Promise<object>} Promise resolving to {webSocketUrl, binding}
85
85
  */
86
86
  (0, _defineProperty2.default)(_this, "register", /*#__PURE__*/function () {
87
87
  var _ref2 = (0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee(llmSocketUrl, datachannelToken) {
@@ -103,8 +103,7 @@ var LLMChannel = exports.default = /*#__PURE__*/function (_ref) {
103
103
  'Data-Channel-Auth-Token': datachannelToken
104
104
  } : {}
105
105
  }).then(function (res) {
106
- _this.webSocketUrl = res.body.webSocketUrl;
107
- _this.binding = res.body.binding;
106
+ return res.body;
108
107
  }).catch(function (error) {
109
108
  _this.logger.error("Error connecting to websocket for : ".concat(error));
110
109
  throw error;
@@ -142,48 +141,72 @@ var LLMChannel = exports.default = /*#__PURE__*/function (_ref) {
142
141
  _this.locusUrl = locusUrl;
143
142
  _this.datachannelUrl = datachannelUrl;
144
143
  var registerStart = performance.now();
145
- _this.connectingPromise = _this.register(datachannelUrl, datachannelToken).then(/*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee2() {
146
- var clientLLMDatachannelResponseTime, isDataChannelTokenEnabled, connectUrl, connectStart, clientLLMWebSocketConnectTime, _t;
147
- return _regenerator.default.wrap(function (_context2) {
148
- while (1) switch (_context2.prev = _context2.next) {
149
- case 0:
150
- clientLLMDatachannelResponseTime = Math.round(performance.now() - registerStart);
151
- _context2.next = 1;
152
- return _this.isDataChannelTokenEnabled();
153
- case 1:
154
- isDataChannelTokenEnabled = _context2.sent;
155
- connectUrl = isDataChannelTokenEnabled && _this.webSocketUrl ? LLMChannel.buildUrlWithAwareSubchannels(_this.webSocketUrl, _constants.AWARE_DATA_CHANNEL) : _this.webSocketUrl;
156
- connectStart = performance.now();
157
- _context2.prev = 2;
158
- _context2.next = 3;
159
- return _this.connect(connectUrl);
160
- case 3:
161
- _context2.next = 5;
162
- break;
163
- case 4:
164
- _context2.prev = 4;
165
- _t = _context2["catch"](2);
166
- // register() succeeded; only connect() failed. Attach the measured datachannel time so
167
- // callers don't misreport a websocket failure as a registration that never completed.
168
- // @ts-ignore
169
- _t.timing = {
170
- clientLLMDatachannelResponseTime: clientLLMDatachannelResponseTime
171
- };
172
- throw _t;
173
- case 5:
174
- clientLLMWebSocketConnectTime = Math.round(performance.now() - connectStart);
175
- return _context2.abrupt("return", {
176
- clientLLMDatachannelResponseTime: clientLLMDatachannelResponseTime,
177
- clientLLMWebSocketConnectTime: clientLLMWebSocketConnectTime
178
- });
179
- case 6:
180
- case "end":
181
- return _context2.stop();
182
- }
183
- }, _callee2, null, [[2, 4]]);
184
- }))).finally(function () {
185
- _this.connectingPromise = undefined;
144
+ var connectionPromise = _this.register(datachannelUrl, datachannelToken).then(/*#__PURE__*/function () {
145
+ var _ref4 = (0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee2(_ref3) {
146
+ var webSocketUrl, binding, clientLLMDatachannelResponseTime, isDataChannelTokenEnabled, connectUrl, connectStart, clientLLMWebSocketConnectTime, _t;
147
+ return _regenerator.default.wrap(function (_context2) {
148
+ while (1) switch (_context2.prev = _context2.next) {
149
+ case 0:
150
+ webSocketUrl = _ref3.webSocketUrl, binding = _ref3.binding;
151
+ if (!(_this.connectingPromise !== connectionPromise)) {
152
+ _context2.next = 1;
153
+ break;
154
+ }
155
+ return _context2.abrupt("return");
156
+ case 1:
157
+ _this.webSocketUrl = webSocketUrl;
158
+ _this.binding = binding;
159
+ clientLLMDatachannelResponseTime = Math.round(performance.now() - registerStart);
160
+ _context2.next = 2;
161
+ return _this.isDataChannelTokenEnabled();
162
+ case 2:
163
+ isDataChannelTokenEnabled = _context2.sent;
164
+ if (!(_this.connectingPromise !== connectionPromise)) {
165
+ _context2.next = 3;
166
+ break;
167
+ }
168
+ return _context2.abrupt("return");
169
+ case 3:
170
+ connectUrl = isDataChannelTokenEnabled && _this.webSocketUrl ? LLMChannel.buildUrlWithAwareSubchannels(_this.webSocketUrl, _constants.AWARE_DATA_CHANNEL) : _this.webSocketUrl;
171
+ connectStart = performance.now();
172
+ _context2.prev = 4;
173
+ _context2.next = 5;
174
+ return _this.connect(connectUrl);
175
+ case 5:
176
+ _context2.next = 7;
177
+ break;
178
+ case 6:
179
+ _context2.prev = 6;
180
+ _t = _context2["catch"](4);
181
+ // register() succeeded; only connect() failed. Attach the measured datachannel time so
182
+ // callers don't misreport a websocket failure as a registration that never completed.
183
+ // @ts-ignore
184
+ _t.timing = {
185
+ clientLLMDatachannelResponseTime: clientLLMDatachannelResponseTime
186
+ };
187
+ throw _t;
188
+ case 7:
189
+ clientLLMWebSocketConnectTime = Math.round(performance.now() - connectStart);
190
+ return _context2.abrupt("return", {
191
+ clientLLMDatachannelResponseTime: clientLLMDatachannelResponseTime,
192
+ clientLLMWebSocketConnectTime: clientLLMWebSocketConnectTime
193
+ });
194
+ case 8:
195
+ case "end":
196
+ return _context2.stop();
197
+ }
198
+ }, _callee2, null, [[4, 6]]);
199
+ }));
200
+ return function (_x3) {
201
+ return _ref4.apply(this, arguments);
202
+ };
203
+ }()).finally(function () {
204
+ // Only clear if this operation is still current
205
+ if (_this.connectingPromise === connectionPromise) {
206
+ _this.connectingPromise = undefined;
207
+ }
186
208
  });
209
+ _this.connectingPromise = connectionPromise;
187
210
  return _this.connectingPromise;
188
211
  });
189
212
  /**
@@ -301,6 +324,11 @@ var LLMChannel = exports.default = /*#__PURE__*/function (_ref) {
301
324
  /**
302
325
  * Disconnects the WebSocket and clears all connection state.
303
326
  * Overrides Mercury's disconnect to also clear LLM-specific state.
327
+ * Clears connectingPromise first to invalidate any in-flight operations,
328
+ * then clears state before calling super.disconnect(). This ensures:
329
+ * 1. Stale register() responses won't overwrite state (they check promise identity)
330
+ * 2. No deadlock with Mercury's _connectWithBackoff() (we don't await connectingPromise)
331
+ * 3. State is cleared before 'disconnected' event fires
304
332
  * @param {object} [options]
305
333
  * @returns {Promise<void>}
306
334
  */
@@ -312,23 +340,26 @@ var LLMChannel = exports.default = /*#__PURE__*/function (_ref) {
312
340
  return _regenerator.default.wrap(function (_context4) {
313
341
  while (1) switch (_context4.prev = _context4.next) {
314
342
  case 0:
315
- _context4.next = 1;
316
- return _superPropGet(LLMChannel, "disconnect", this, 3)([options]);
317
- case 1:
343
+ // Clear connectingPromise first to invalidate any in-flight operations.
344
+ // Their promise identity checks will detect they've been superseded.
345
+ this.connectingPromise = undefined;
346
+
347
+ // Clear LLM-specific state before Mercury emits 'disconnected'
318
348
  this.webSocketUrl = undefined;
319
349
  this.binding = undefined;
320
350
  this.locusUrl = undefined;
321
351
  this.datachannelUrl = undefined;
322
352
  this.datachannelToken = undefined;
323
353
  this.refreshHandler = undefined;
324
- this.emit('disconnected');
325
- case 2:
354
+ _context4.next = 1;
355
+ return _superPropGet(LLMChannel, "disconnect", this, 3)([options]);
356
+ case 1:
326
357
  case "end":
327
358
  return _context4.stop();
328
359
  }
329
360
  }, _callee4, this);
330
361
  }));
331
- function disconnect(_x3) {
362
+ function disconnect(_x4) {
332
363
  return _disconnect.apply(this, arguments);
333
364
  }
334
365
  return disconnect;
package/dist/llm.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"names":["_internalPluginMercury","_interopRequireDefault","require","_constants","_callSuper","t","o","e","_getPrototypeOf2","default","_possibleConstructorReturn2","_isNativeReflectConstruct","_Reflect$construct","constructor","apply","Boolean","prototype","valueOf","call","_superPropGet","r","p","_get2","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","_defineProperty2","LLM","_ref2","_asyncToGenerator2","_regenerator","mark","_callee","llmSocketUrl","datachannelToken","isDataChannelTokenEnabled","wrap","_context","prev","next","sent","abrupt","request","method","url","body","deviceUrl","webex","internal","device","headers","then","res","webSocketUrl","binding","catch","error","logger","stop","_x","_x2","locusUrl","datachannelUrl","connectingPromise","isConnected","_promise","resolve","registerStart","performance","now","register","_callee2","clientLLMDatachannelResponseTime","connectUrl","connectStart","clientLLMWebSocketConnectTime","_t","_context2","Math","round","buildUrlWithAwareSubchannels","AWARE_DATA_CHANNEL","connect","timing","finally","undefined","connected","token","handler","refreshHandler","_inherits2","_createClass2","key","value","isConnecting","_refreshDataChannelToken","_callee3","_t2","_context3","warn","message","refreshDataChannelToken","_disconnect","_callee4","options","_context4","emit","disconnect","_x3","feature","getFeature","DATA_CHANNEL_WITH_JWT_TOKEN","matchesDatachannelRequestUrl","requestUrl","registrationUrl","startsWith","URL","registration","pathname","Mercury","baseUrl","subchannels","urlObj","searchParams","set","SUBSCRIPTION_AWARE_SUBCHANNELS_PARAM","join","toString"],"sources":["llm.ts"],"sourcesContent":["/* eslint-disable consistent-return */\nimport Mercury from '@webex/internal-plugin-mercury';\n\nimport {\n LLM,\n DATA_CHANNEL_WITH_JWT_TOKEN,\n AWARE_DATA_CHANNEL,\n SUBSCRIPTION_AWARE_SUBCHANNELS_PARAM,\n} from './constants';\nimport {ILLMChannel, DataChannelTokenType, RegisterAndConnectTiming} 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 — a single WebSocket connection to the LLM data channel.\n *\n * Created via `webex.internal.llm.createChannel()`. The caller owns the\n * channel and is responsible for its lifecycle (connect, disconnect, cleanup).\n * Multiple LLMChannels can exist simultaneously for different meetings.\n */\nexport default class LLMChannel extends (Mercury as any) implements ILLMChannel {\n namespace = LLM;\n\n private webSocketUrl?: string;\n private binding?: string;\n private locusUrl?: string;\n private datachannelUrl?: string;\n private datachannelToken?: string;\n private refreshHandler?: () => Promise<{\n body: {datachannelToken: string; datachannelTokenType: DataChannelTokenType};\n }>;\n\n /** In-flight connection promise for deduplication. */\n private connectingPromise?: Promise<RegisterAndConnectTiming | void>;\n\n /**\n * Check if a connection is currently in progress.\n * @returns {boolean} True if connecting\n */\n public isConnecting(): boolean {\n return !!this.connectingPromise;\n }\n\n /**\n * Register to the websocket\n * @param {string} llmSocketUrl\n * @param {string} datachannelToken\n * @returns {Promise<void>}\n */\n private register = async (llmSocketUrl: string, datachannelToken?: string): 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 this.webSocketUrl = res.body.webSocketUrl;\n this.binding = res.body.binding;\n })\n .catch((error: any) => {\n this.logger.error(`Error connecting to websocket for : ${error}`);\n throw error;\n });\n };\n\n /**\n * Register and connect to the websocket.\n * Handles deduplication: returns existing promise if connection in progress,\n * or resolves immediately if already connected to the same URLs.\n * @param {string} locusUrl\n * @param {string} datachannelUrl\n * @param {string} datachannelToken\n * @returns {Promise<RegisterAndConnectTiming | void>}\n */\n public registerAndConnect = (\n locusUrl: string,\n datachannelUrl: string,\n datachannelToken?: string\n ): Promise<RegisterAndConnectTiming | void> => {\n // Deduplicate concurrent calls while a connection is in-flight\n if (this.connectingPromise) {\n return this.connectingPromise;\n }\n\n // If already connected to the exact same datachannel URL, avoid triggering\n // a reconnect that would cause the server to replace with 4000 Replaced\n if (\n this.isConnected() &&\n this.datachannelUrl === datachannelUrl &&\n this.locusUrl === locusUrl\n ) {\n return Promise.resolve();\n }\n\n this.locusUrl = locusUrl;\n this.datachannelUrl = datachannelUrl;\n const registerStart = performance.now();\n\n this.connectingPromise = this.register(datachannelUrl, datachannelToken)\n .then(async () => {\n const clientLLMDatachannelResponseTime = Math.round(performance.now() - registerStart);\n const isDataChannelTokenEnabled = await this.isDataChannelTokenEnabled();\n\n const connectUrl =\n isDataChannelTokenEnabled && this.webSocketUrl\n ? LLMChannel.buildUrlWithAwareSubchannels(this.webSocketUrl, AWARE_DATA_CHANNEL)\n : this.webSocketUrl;\n\n const connectStart = performance.now();\n\n try {\n await this.connect(connectUrl);\n } catch (error) {\n // register() succeeded; only connect() failed. Attach the measured datachannel time so\n // callers don't misreport a websocket failure as a registration that never completed.\n // @ts-ignore\n error.timing = {clientLLMDatachannelResponseTime};\n throw error;\n }\n\n const clientLLMWebSocketConnectTime = Math.round(performance.now() - connectStart);\n\n return {clientLLMDatachannelResponseTime, clientLLMWebSocketConnectTime};\n })\n .finally(() => {\n this.connectingPromise = undefined;\n });\n\n return this.connectingPromise;\n };\n\n /**\n * Tells if LLM socket is connected\n * @returns {boolean} connected\n */\n public isConnected = (): boolean => this.connected;\n\n /**\n * Tells if LLM socket is binding\n * @returns {string | undefined} binding\n */\n public getBinding = (): string | undefined => this.binding;\n\n /**\n * Get Locus URL for the connection\n * @returns {string | undefined} locus Url\n */\n public getLocusUrl = (): string | undefined => this.locusUrl;\n\n /**\n * Get data channel URL for the connection\n * @returns {string | undefined} data channel Url\n */\n public getDatachannelUrl = (): string | undefined => this.datachannelUrl;\n\n /**\n * Get data channel token for this connection.\n * @returns {string | undefined}\n */\n public getDatachannelToken = (): string | undefined => this.datachannelToken;\n\n /**\n * Store the data channel token for this connection.\n * @param {string} token\n * @returns {void}\n */\n public setDatachannelToken = (token: string): void => {\n this.datachannelToken = token;\n };\n\n /**\n * Clear the data channel token for this connection.\n * @returns {void}\n */\n public clearDatachannelToken = (): void => {\n this.datachannelToken = undefined;\n };\n\n /**\n * Set the handler used to refresh the DataChannel token.\n * @param {function} handler\n * @returns {void}\n */\n public setRefreshHandler = (\n handler: () => Promise<{\n body: {datachannelToken: string; datachannelTokenType: DataChannelTokenType};\n }>\n ): void => {\n this.refreshHandler = handler;\n };\n\n /**\n * Refresh the data channel token using the injected handler.\n * @returns {Promise<object | null>}\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 return await this.refreshHandler();\n } catch (error: any) {\n this.logger.warn(\n `llm#refreshDataChannelToken --> DataChannel token refresh failed: ${\n error?.message || error\n }`\n );\n\n return null;\n }\n }\n\n /**\n * Disconnects the WebSocket and clears all connection state.\n * Overrides Mercury's disconnect to also clear LLM-specific state.\n * @param {object} [options]\n * @returns {Promise<void>}\n */\n public async disconnect(options?: {code: number; reason: string}): Promise<void> {\n await super.disconnect(options);\n this.webSocketUrl = undefined;\n this.binding = undefined;\n this.locusUrl = undefined;\n this.datachannelUrl = undefined;\n this.datachannelToken = undefined;\n this.refreshHandler = undefined;\n this.emit('disconnected');\n }\n\n /**\n * Matches a request URL to a stored datachannel registration URL.\n * Host can differ (e.g. rewritten by hostmap interceptor), so we first\n * try full URL prefix and then fall back to pathname prefix.\n * @param {string} requestUrl\n * @param {string} registrationUrl\n * @returns {boolean}\n */\n public static matchesDatachannelRequestUrl(requestUrl: string, registrationUrl: string): boolean {\n if (!requestUrl || !registrationUrl) {\n return false;\n }\n\n if (requestUrl.startsWith(registrationUrl)) {\n return true;\n }\n\n try {\n const request = new URL(requestUrl);\n const registration = new URL(registrationUrl);\n\n return request.pathname.startsWith(registration.pathname);\n } catch (error) {\n return false;\n }\n }\n\n /**\n * Returns true if the data channel token feature flag is enabled.\n * @returns {Promise<boolean>}\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 * @param {string} baseUrl\n * @param {string[]} subchannels\n * @returns {string}\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":";;;;;;;;;;;;;;;;;;;AACA,IAAAA,sBAAA,GAAAC,sBAAA,CAAAC,OAAA;AAEA,IAAAC,UAAA,GAAAD,OAAA;AAKqB,SAAAE,WAAAC,CAAA,EAAAC,CAAA,EAAAC,CAAA,WAAAD,CAAA,OAAAE,gBAAA,CAAAC,OAAA,EAAAH,CAAA,OAAAI,2BAAA,CAAAD,OAAA,EAAAJ,CAAA,EAAAM,yBAAA,KAAAC,kBAAA,CAAAN,CAAA,EAAAC,CAAA,YAAAC,gBAAA,CAAAC,OAAA,EAAAJ,CAAA,EAAAQ,WAAA,IAAAP,CAAA,CAAAQ,KAAA,CAAAT,CAAA,EAAAE,CAAA;AAAA,SAAAI,0BAAA,cAAAN,CAAA,IAAAU,OAAA,CAAAC,SAAA,CAAAC,OAAA,CAAAC,IAAA,CAAAN,kBAAA,CAAAG,OAAA,iCAAAV,CAAA,aAAAM,yBAAA,YAAAA,0BAAA,aAAAN,CAAA;AAAA,SAAAc,cAAAd,CAAA,EAAAC,CAAA,EAAAC,CAAA,EAAAa,CAAA,QAAAC,CAAA,OAAAC,KAAA,CAAAb,OAAA,MAAAD,gBAAA,CAAAC,OAAA,MAAAW,CAAA,GAAAf,CAAA,CAAAW,SAAA,GAAAX,CAAA,GAAAC,CAAA,EAAAC,CAAA,cAAAa,CAAA,yBAAAC,CAAA,aAAAhB,CAAA,WAAAgB,CAAA,CAAAP,KAAA,CAAAP,CAAA,EAAAF,CAAA,OAAAgB,CAAA,IARrB;AAWO,IAAME,MAAM,GAAAC,OAAA,CAAAD,MAAA,GAAG;EACpBE,GAAG,EAAE;IACH;AACJ;AACA;AACA;IACIC,YAAY,EAAEC,OAAO,CAACC,GAAG,CAACC,qBAAqB,IAAI,KAAK;IACxD;AACJ;AACA;AACA;IACIC,WAAW,EAAEH,OAAO,CAACC,GAAG,CAACG,oBAAoB,IAAI,KAAK;IACtD;AACJ;AACA;AACA;IACIC,cAAc,EAAEL,OAAO,CAACC,GAAG,CAACK,wBAAwB,IAAI,KAAK;IAC7D;AACJ;AACA;AACA;IACIC,gBAAgB,EAAEP,OAAO,CAACC,GAAG,CAACO,0BAA0B,IAAI,IAAI;IAChE;AACJ;AACA;AACA;AACA;IACIC,eAAe,EAAET,OAAO,CAACC,GAAG,CAACS,yBAAyB,IAAI;EAC5D;AACF,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA;AACA;AANA,IAOqBC,UAAU,GAAAd,OAAA,CAAAf,OAAA,0BAAA8B,IAAA;EAAA,SAAAD,WAAA;IAAA,IAAAE,KAAA;IAAA,IAAAC,gBAAA,CAAAhC,OAAA,QAAA6B,UAAA;IAAA,SAAAI,IAAA,GAAAC,SAAA,CAAAC,MAAA,EAAAC,IAAA,OAAAC,KAAA,CAAAJ,IAAA,GAAAK,IAAA,MAAAA,IAAA,GAAAL,IAAA,EAAAK,IAAA;MAAAF,IAAA,CAAAE,IAAA,IAAAJ,SAAA,CAAAI,IAAA;IAAA;IAAAP,KAAA,GAAApC,UAAA,OAAAkC,UAAA,KAAAU,MAAA,CAAAH,IAAA;IAAA,IAAAI,gBAAA,CAAAxC,OAAA,EAAA+B,KAAA,eACjBU,cAAG;IAAA,IAAAD,gBAAA,CAAAxC,OAAA,EAAA+B,KAAA;IAAA,IAAAS,gBAAA,CAAAxC,OAAA,EAAA+B,KAAA;IAAA,IAAAS,gBAAA,CAAAxC,OAAA,EAAA+B,KAAA;IAAA,IAAAS,gBAAA,CAAAxC,OAAA,EAAA+B,KAAA;IAAA,IAAAS,gBAAA,CAAAxC,OAAA,EAAA+B,KAAA;IAAA,IAAAS,gBAAA,CAAAxC,OAAA,EAAA+B,KAAA;IAWf;IAAA,IAAAS,gBAAA,CAAAxC,OAAA,EAAA+B,KAAA;IAWA;AACF;AACA;AACA;AACA;AACA;IALE,IAAAS,gBAAA,CAAAxC,OAAA,EAAA+B,KAAA;MAAA,IAAAW,KAAA,OAAAC,kBAAA,CAAA3C,OAAA,eAAA4C,YAAA,CAAA5C,OAAA,CAAA6C,IAAA,CAMmB,SAAAC,QAAOC,YAAoB,EAAEC,gBAAyB;QAAA,IAAAC,yBAAA;QAAA,OAAAL,YAAA,CAAA5C,OAAA,CAAAkD,IAAA,WAAAC,QAAA;UAAA,kBAAAA,QAAA,CAAAC,IAAA,GAAAD,QAAA,CAAAE,IAAA;YAAA;cAAAF,QAAA,CAAAE,IAAA;cAAA,OAC/BtB,KAAA,CAAKkB,yBAAyB,CAAC,CAAC;YAAA;cAAlEA,yBAAyB,GAAAE,QAAA,CAAAG,IAAA;cAAA,OAAAH,QAAA,CAAAI,MAAA,WAExBxB,KAAA,CAAKyB,OAAO,CAAC;gBAClBC,MAAM,EAAE,MAAM;gBACdC,GAAG,EAAEX,YAAY;gBACjBY,IAAI,EAAE;kBAACC,SAAS,EAAE7B,KAAA,CAAK8B,KAAK,CAACC,QAAQ,CAACC,MAAM,CAACL;gBAAG,CAAC;gBACjDM,OAAO,EACLf,yBAAyB,IAAID,gBAAgB,GACzC;kBAAC,yBAAyB,EAAEA;gBAAgB,CAAC,GAC7C,CAAC;cACT,CAAC,CAAC,CACCiB,IAAI,CAAC,UAACC,GAAoD,EAAK;gBAC9DnC,KAAA,CAAKoC,YAAY,GAAGD,GAAG,CAACP,IAAI,CAACQ,YAAY;gBACzCpC,KAAA,CAAKqC,OAAO,GAAGF,GAAG,CAACP,IAAI,CAACS,OAAO;cACjC,CAAC,CAAC,CACDC,KAAK,CAAC,UAACC,KAAU,EAAK;gBACrBvC,KAAA,CAAKwC,MAAM,CAACD,KAAK,wCAAA/B,MAAA,CAAwC+B,KAAK,CAAE,CAAC;gBACjE,MAAMA,KAAK;cACb,CAAC,CAAC;YAAA;YAAA;cAAA,OAAAnB,QAAA,CAAAqB,IAAA;UAAA;QAAA,GAAA1B,OAAA;MAAA,CACL;MAAA,iBAAA2B,EAAA,EAAAC,GAAA;QAAA,OAAAhC,KAAA,CAAArC,KAAA,OAAA6B,SAAA;MAAA;IAAA;IAED;AACF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;IARE,IAAAM,gBAAA,CAAAxC,OAAA,EAAA+B,KAAA,wBAS4B,UAC1B4C,QAAgB,EAChBC,cAAsB,EACtB5B,gBAAyB,EACoB;MAC7C;MACA,IAAIjB,KAAA,CAAK8C,iBAAiB,EAAE;QAC1B,OAAO9C,KAAA,CAAK8C,iBAAiB;MAC/B;;MAEA;MACA;MACA,IACE9C,KAAA,CAAK+C,WAAW,CAAC,CAAC,IAClB/C,KAAA,CAAK6C,cAAc,KAAKA,cAAc,IACtC7C,KAAA,CAAK4C,QAAQ,KAAKA,QAAQ,EAC1B;QACA,OAAOI,QAAA,CAAA/E,OAAA,CAAQgF,OAAO,CAAC,CAAC;MAC1B;MAEAjD,KAAA,CAAK4C,QAAQ,GAAGA,QAAQ;MACxB5C,KAAA,CAAK6C,cAAc,GAAGA,cAAc;MACpC,IAAMK,aAAa,GAAGC,WAAW,CAACC,GAAG,CAAC,CAAC;MAEvCpD,KAAA,CAAK8C,iBAAiB,GAAG9C,KAAA,CAAKqD,QAAQ,CAACR,cAAc,EAAE5B,gBAAgB,CAAC,CACrEiB,IAAI,kBAAAtB,kBAAA,CAAA3C,OAAA,eAAA4C,YAAA,CAAA5C,OAAA,CAAA6C,IAAA,CAAC,SAAAwC,SAAA;QAAA,IAAAC,gCAAA,EAAArC,yBAAA,EAAAsC,UAAA,EAAAC,YAAA,EAAAC,6BAAA,EAAAC,EAAA;QAAA,OAAA9C,YAAA,CAAA5C,OAAA,CAAAkD,IAAA,WAAAyC,SAAA;UAAA,kBAAAA,SAAA,CAAAvC,IAAA,GAAAuC,SAAA,CAAAtC,IAAA;YAAA;cACEiC,gCAAgC,GAAGM,IAAI,CAACC,KAAK,CAACX,WAAW,CAACC,GAAG,CAAC,CAAC,GAAGF,aAAa,CAAC;cAAAU,SAAA,CAAAtC,IAAA;cAAA,OAC9CtB,KAAA,CAAKkB,yBAAyB,CAAC,CAAC;YAAA;cAAlEA,yBAAyB,GAAA0C,SAAA,CAAArC,IAAA;cAEzBiC,UAAU,GACdtC,yBAAyB,IAAIlB,KAAA,CAAKoC,YAAY,GAC1CtC,UAAU,CAACiE,4BAA4B,CAAC/D,KAAA,CAAKoC,YAAY,EAAE4B,6BAAkB,CAAC,GAC9EhE,KAAA,CAAKoC,YAAY;cAEjBqB,YAAY,GAAGN,WAAW,CAACC,GAAG,CAAC,CAAC;cAAAQ,SAAA,CAAAvC,IAAA;cAAAuC,SAAA,CAAAtC,IAAA;cAAA,OAG9BtB,KAAA,CAAKiE,OAAO,CAACT,UAAU,CAAC;YAAA;cAAAI,SAAA,CAAAtC,IAAA;cAAA;YAAA;cAAAsC,SAAA,CAAAvC,IAAA;cAAAsC,EAAA,GAAAC,SAAA;cAE9B;cACA;cACA;cACAD,EAAA,CAAMO,MAAM,GAAG;gBAACX,gCAAgC,EAAhCA;cAAgC,CAAC;cAAC,MAAAI,EAAA;YAAA;cAI9CD,6BAA6B,GAAGG,IAAI,CAACC,KAAK,CAACX,WAAW,CAACC,GAAG,CAAC,CAAC,GAAGK,YAAY,CAAC;cAAA,OAAAG,SAAA,CAAApC,MAAA,WAE3E;gBAAC+B,gCAAgC,EAAhCA,gCAAgC;gBAAEG,6BAA6B,EAA7BA;cAA6B,CAAC;YAAA;YAAA;cAAA,OAAAE,SAAA,CAAAnB,IAAA;UAAA;QAAA,GAAAa,QAAA;MAAA,CACzE,GAAC,CACDa,OAAO,CAAC,YAAM;QACbnE,KAAA,CAAK8C,iBAAiB,GAAGsB,SAAS;MACpC,CAAC,CAAC;MAEJ,OAAOpE,KAAA,CAAK8C,iBAAiB;IAC/B,CAAC;IAED;AACF;AACA;AACA;IAHE,IAAArC,gBAAA,CAAAxC,OAAA,EAAA+B,KAAA,iBAIqB;MAAA,OAAeA,KAAA,CAAKqE,SAAS;IAAA;IAElD;AACF;AACA;AACA;IAHE,IAAA5D,gBAAA,CAAAxC,OAAA,EAAA+B,KAAA,gBAIoB;MAAA,OAA0BA,KAAA,CAAKqC,OAAO;IAAA;IAE1D;AACF;AACA;AACA;IAHE,IAAA5B,gBAAA,CAAAxC,OAAA,EAAA+B,KAAA,iBAIqB;MAAA,OAA0BA,KAAA,CAAK4C,QAAQ;IAAA;IAE5D;AACF;AACA;AACA;IAHE,IAAAnC,gBAAA,CAAAxC,OAAA,EAAA+B,KAAA,uBAI2B;MAAA,OAA0BA,KAAA,CAAK6C,cAAc;IAAA;IAExE;AACF;AACA;AACA;IAHE,IAAApC,gBAAA,CAAAxC,OAAA,EAAA+B,KAAA,yBAI6B;MAAA,OAA0BA,KAAA,CAAKiB,gBAAgB;IAAA;IAE5E;AACF;AACA;AACA;AACA;IAJE,IAAAR,gBAAA,CAAAxC,OAAA,EAAA+B,KAAA,yBAK6B,UAACsE,KAAa,EAAW;MACpDtE,KAAA,CAAKiB,gBAAgB,GAAGqD,KAAK;IAC/B,CAAC;IAED;AACF;AACA;AACA;IAHE,IAAA7D,gBAAA,CAAAxC,OAAA,EAAA+B,KAAA,2BAI+B,YAAY;MACzCA,KAAA,CAAKiB,gBAAgB,GAAGmD,SAAS;IACnC,CAAC;IAED;AACF;AACA;AACA;AACA;IAJE,IAAA3D,gBAAA,CAAAxC,OAAA,EAAA+B,KAAA,uBAK2B,UACzBuE,OAEE,EACO;MACTvE,KAAA,CAAKwE,cAAc,GAAGD,OAAO;IAC/B,CAAC;IAAA,OAAAvE,KAAA;EAAA;EAAA,IAAAyE,UAAA,CAAAxG,OAAA,EAAA6B,UAAA,EAAAC,IAAA;EAAA,WAAA2E,aAAA,CAAAzG,OAAA,EAAA6B,UAAA;IAAA6E,GAAA;IAAAC,KAAA;IAhKD;AACF;AACA;AACA;IACE,SAAOC,YAAYA,CAAA,EAAY;MAC7B,OAAO,CAAC,CAAC,IAAI,CAAC/B,iBAAiB;IACjC;EAAC;IAAA6B,GAAA;IAAAC,KAAA;IA4JD;AACF;AACA;AACA;IAHE;MAAA,IAAAE,wBAAA,OAAAlE,kBAAA,CAAA3C,OAAA,eAAA4C,YAAA,CAAA5C,OAAA,CAAA6C,IAAA,CAIA,SAAAiE,SAAA;QAAA,IAAAC,GAAA;QAAA,OAAAnE,YAAA,CAAA5C,OAAA,CAAAkD,IAAA,WAAA8D,SAAA;UAAA,kBAAAA,SAAA,CAAA5D,IAAA,GAAA4D,SAAA,CAAA3D,IAAA;YAAA;cAAA,IACO,IAAI,CAACkD,cAAc;gBAAAS,SAAA,CAAA3D,IAAA;gBAAA;cAAA;cACtB,IAAI,CAACkB,MAAM,CAAC0C,IAAI,CACd,uFACF,CAAC;cAAC,OAAAD,SAAA,CAAAzD,MAAA,WAEK,IAAI;YAAA;cAAAyD,SAAA,CAAA5D,IAAA;cAAA4D,SAAA,CAAA3D,IAAA;cAAA,OAIE,IAAI,CAACkD,cAAc,CAAC,CAAC;YAAA;cAAA,OAAAS,SAAA,CAAAzD,MAAA,WAAAyD,SAAA,CAAA1D,IAAA;YAAA;cAAA0D,SAAA,CAAA5D,IAAA;cAAA2D,GAAA,GAAAC,SAAA;cAElC,IAAI,CAACzC,MAAM,CAAC0C,IAAI,sEAAA1E,MAAA,CAEZ,CAAAwE,GAAA,aAAAA,GAAA,uBAAAA,GAAA,CAAOG,OAAO,KAAAH,GAAS,CAE3B,CAAC;cAAC,OAAAC,SAAA,CAAAzD,MAAA,WAEK,IAAI;YAAA;YAAA;cAAA,OAAAyD,SAAA,CAAAxC,IAAA;UAAA;QAAA,GAAAsC,QAAA;MAAA,CAEd;MAAA,SApBYK,uBAAuBA,CAAA;QAAA,OAAAN,wBAAA,CAAAxG,KAAA,OAAA6B,SAAA;MAAA;MAAA,OAAvBiF,uBAAuB;IAAA;IAsBpC;AACF;AACA;AACA;AACA;AACA;IALE;EAAA;IAAAT,GAAA;IAAAC,KAAA;MAAA,IAAAS,WAAA,OAAAzE,kBAAA,CAAA3C,OAAA,eAAA4C,YAAA,CAAA5C,OAAA,CAAA6C,IAAA,CAMA,SAAAwE,SAAwBC,OAAwC;QAAA,OAAA1E,YAAA,CAAA5C,OAAA,CAAAkD,IAAA,WAAAqE,SAAA;UAAA,kBAAAA,SAAA,CAAAnE,IAAA,GAAAmE,SAAA,CAAAlE,IAAA;YAAA;cAAAkE,SAAA,CAAAlE,IAAA;cAAA,OAAA3C,aAAA,CAAAmB,UAAA,0BACvCyF,OAAO;YAAA;cAC9B,IAAI,CAACnD,YAAY,GAAGgC,SAAS;cAC7B,IAAI,CAAC/B,OAAO,GAAG+B,SAAS;cACxB,IAAI,CAACxB,QAAQ,GAAGwB,SAAS;cACzB,IAAI,CAACvB,cAAc,GAAGuB,SAAS;cAC/B,IAAI,CAACnD,gBAAgB,GAAGmD,SAAS;cACjC,IAAI,CAACI,cAAc,GAAGJ,SAAS;cAC/B,IAAI,CAACqB,IAAI,CAAC,cAAc,CAAC;YAAC;YAAA;cAAA,OAAAD,SAAA,CAAA/C,IAAA;UAAA;QAAA,GAAA6C,QAAA;MAAA,CAC3B;MAAA,SATYI,UAAUA,CAAAC,GAAA;QAAA,OAAAN,WAAA,CAAA/G,KAAA,OAAA6B,SAAA;MAAA;MAAA,OAAVuF,UAAU;IAAA;IAWvB;AACF;AACA;AACA;AACA;AACA;AACA;AACA;IAPE;EAAA;IAAAf,GAAA;IAAAC,KAAA;IA2BA;AACF;AACA;AACA;IACE,SAAO1D,yBAAyBA,CAAA,EAAqB;MACnD;MACA,OAAO,IAAI,CAACY,KAAK,CAACC,QAAQ,CAAC6D,OAAO,CAACC,UAAU,CAAC,WAAW,EAAEC,sCAA2B,CAAC;IACzF;;IAEA;AACF;AACA;AACA;AACA;AACA;EALE;IAAAnB,GAAA;IAAAC,KAAA,EA5BA,SAAcmB,4BAA4BA,CAACC,UAAkB,EAAEC,eAAuB,EAAW;MAC/F,IAAI,CAACD,UAAU,IAAI,CAACC,eAAe,EAAE;QACnC,OAAO,KAAK;MACd;MAEA,IAAID,UAAU,CAACE,UAAU,CAACD,eAAe,CAAC,EAAE;QAC1C,OAAO,IAAI;MACb;MAEA,IAAI;QACF,IAAMxE,OAAO,GAAG,IAAI0E,GAAG,CAACH,UAAU,CAAC;QACnC,IAAMI,YAAY,GAAG,IAAID,GAAG,CAACF,eAAe,CAAC;QAE7C,OAAOxE,OAAO,CAAC4E,QAAQ,CAACH,UAAU,CAACE,YAAY,CAACC,QAAQ,CAAC;MAC3D,CAAC,CAAC,OAAO9D,KAAK,EAAE;QACd,OAAO,KAAK;MACd;IACF;EAAC;AAAA,EArPsC+D,8BAAO;AAAA,IAAA7F,gBAAA,CAAAxC,OAAA,EAA3B6B,UAAU,kCAsQgB,UAACyG,OAAe,EAAEC,WAAqB,EAAK;EACvF,IAAMC,MAAM,GAAG,IAAIN,GAAG,CAACI,OAAO,CAAC;EAC/BE,MAAM,CAACC,YAAY,CAACC,GAAG,CAACC,+CAAoC,EAAEJ,WAAW,CAACK,IAAI,CAAC,GAAG,CAAC,CAAC;EAEpF,OAAOJ,MAAM,CAACK,QAAQ,CAAC,CAAC;AAC1B,CAAC","ignoreList":[]}
1
+ {"version":3,"names":["_internalPluginMercury","_interopRequireDefault","require","_constants","_callSuper","t","o","e","_getPrototypeOf2","default","_possibleConstructorReturn2","_isNativeReflectConstruct","_Reflect$construct","constructor","apply","Boolean","prototype","valueOf","call","_superPropGet","r","p","_get2","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","_defineProperty2","LLM","_ref2","_asyncToGenerator2","_regenerator","mark","_callee","llmSocketUrl","datachannelToken","isDataChannelTokenEnabled","wrap","_context","prev","next","sent","abrupt","request","method","url","body","deviceUrl","webex","internal","device","headers","then","res","catch","error","logger","stop","_x","_x2","locusUrl","datachannelUrl","connectingPromise","isConnected","_promise","resolve","registerStart","performance","now","connectionPromise","register","_ref4","_callee2","_ref3","webSocketUrl","binding","clientLLMDatachannelResponseTime","connectUrl","connectStart","clientLLMWebSocketConnectTime","_t","_context2","Math","round","buildUrlWithAwareSubchannels","AWARE_DATA_CHANNEL","connect","timing","_x3","finally","undefined","connected","token","handler","refreshHandler","_inherits2","_createClass2","key","value","isConnecting","_refreshDataChannelToken","_callee3","_t2","_context3","warn","message","refreshDataChannelToken","_disconnect","_callee4","options","_context4","disconnect","_x4","feature","getFeature","DATA_CHANNEL_WITH_JWT_TOKEN","matchesDatachannelRequestUrl","requestUrl","registrationUrl","startsWith","URL","registration","pathname","Mercury","baseUrl","subchannels","urlObj","searchParams","set","SUBSCRIPTION_AWARE_SUBCHANNELS_PARAM","join","toString"],"sources":["llm.ts"],"sourcesContent":["/* eslint-disable consistent-return */\nimport Mercury from '@webex/internal-plugin-mercury';\n\nimport {\n LLM,\n DATA_CHANNEL_WITH_JWT_TOKEN,\n AWARE_DATA_CHANNEL,\n SUBSCRIPTION_AWARE_SUBCHANNELS_PARAM,\n} from './constants';\nimport {ILLMChannel, DataChannelTokenType, RegisterAndConnectTiming} 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 — a single WebSocket connection to the LLM data channel.\n *\n * Created via `webex.internal.llm.createChannel()`. The caller owns the\n * channel and is responsible for its lifecycle (connect, disconnect, cleanup).\n * Multiple LLMChannels can exist simultaneously for different meetings.\n */\nexport default class LLMChannel extends (Mercury as any) implements ILLMChannel {\n namespace = LLM;\n\n private webSocketUrl?: string;\n private binding?: string;\n private locusUrl?: string;\n private datachannelUrl?: string;\n private datachannelToken?: string;\n private refreshHandler?: () => Promise<{\n body: {datachannelToken: string; datachannelTokenType: DataChannelTokenType};\n }>;\n\n /** In-flight connection promise for deduplication. */\n private connectingPromise?: Promise<RegisterAndConnectTiming | void>;\n\n /**\n * Check if a connection is currently in progress.\n * @returns {boolean} True if connecting\n */\n public isConnecting(): boolean {\n return !!this.connectingPromise;\n }\n\n /**\n * Register to the websocket\n * @param {string} llmSocketUrl\n * @param {string} datachannelToken\n * @returns {Promise<object>} Promise resolving to {webSocketUrl, binding}\n */\n private register = async (\n llmSocketUrl: string,\n datachannelToken?: string\n ): Promise<{webSocketUrl: string; binding: string}> => {\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}}) => res.body)\n .catch((error: any) => {\n this.logger.error(`Error connecting to websocket for : ${error}`);\n throw error;\n });\n };\n\n /**\n * Register and connect to the websocket.\n * Handles deduplication: returns existing promise if connection in progress,\n * or resolves immediately if already connected to the same URLs.\n * @param {string} locusUrl\n * @param {string} datachannelUrl\n * @param {string} datachannelToken\n * @returns {Promise<RegisterAndConnectTiming | void>}\n */\n public registerAndConnect = (\n locusUrl: string,\n datachannelUrl: string,\n datachannelToken?: string\n ): Promise<RegisterAndConnectTiming | void> => {\n // Deduplicate concurrent calls while a connection is in-flight\n if (this.connectingPromise) {\n return this.connectingPromise;\n }\n\n // If already connected to the exact same datachannel URL, avoid triggering\n // a reconnect that would cause the server to replace with 4000 Replaced\n if (\n this.isConnected() &&\n this.datachannelUrl === datachannelUrl &&\n this.locusUrl === locusUrl\n ) {\n return Promise.resolve();\n }\n\n this.locusUrl = locusUrl;\n this.datachannelUrl = datachannelUrl;\n const registerStart = performance.now();\n\n const connectionPromise: Promise<RegisterAndConnectTiming | void> = this.register(\n datachannelUrl,\n datachannelToken\n )\n .then(async ({webSocketUrl, binding}) => {\n // Check if this operation was invalidated by a disconnect\n if (this.connectingPromise !== connectionPromise) {\n return;\n }\n\n this.webSocketUrl = webSocketUrl;\n this.binding = binding;\n\n const clientLLMDatachannelResponseTime = Math.round(performance.now() - registerStart);\n const isDataChannelTokenEnabled = await this.isDataChannelTokenEnabled();\n\n // Check again after async operation - disconnect may have run during the await\n if (this.connectingPromise !== connectionPromise) {\n return;\n }\n\n const connectUrl =\n isDataChannelTokenEnabled && this.webSocketUrl\n ? LLMChannel.buildUrlWithAwareSubchannels(this.webSocketUrl, AWARE_DATA_CHANNEL)\n : this.webSocketUrl;\n\n const connectStart = performance.now();\n\n try {\n await this.connect(connectUrl);\n } catch (error) {\n // register() succeeded; only connect() failed. Attach the measured datachannel time so\n // callers don't misreport a websocket failure as a registration that never completed.\n // @ts-ignore\n error.timing = {clientLLMDatachannelResponseTime};\n throw error;\n }\n\n const clientLLMWebSocketConnectTime = Math.round(performance.now() - connectStart);\n\n return {clientLLMDatachannelResponseTime, clientLLMWebSocketConnectTime};\n })\n .finally(() => {\n // Only clear if this operation is still current\n if (this.connectingPromise === connectionPromise) {\n this.connectingPromise = undefined;\n }\n });\n\n this.connectingPromise = connectionPromise;\n\n return this.connectingPromise;\n };\n\n /**\n * Tells if LLM socket is connected\n * @returns {boolean} connected\n */\n public isConnected = (): boolean => this.connected;\n\n /**\n * Tells if LLM socket is binding\n * @returns {string | undefined} binding\n */\n public getBinding = (): string | undefined => this.binding;\n\n /**\n * Get Locus URL for the connection\n * @returns {string | undefined} locus Url\n */\n public getLocusUrl = (): string | undefined => this.locusUrl;\n\n /**\n * Get data channel URL for the connection\n * @returns {string | undefined} data channel Url\n */\n public getDatachannelUrl = (): string | undefined => this.datachannelUrl;\n\n /**\n * Get data channel token for this connection.\n * @returns {string | undefined}\n */\n public getDatachannelToken = (): string | undefined => this.datachannelToken;\n\n /**\n * Store the data channel token for this connection.\n * @param {string} token\n * @returns {void}\n */\n public setDatachannelToken = (token: string): void => {\n this.datachannelToken = token;\n };\n\n /**\n * Clear the data channel token for this connection.\n * @returns {void}\n */\n public clearDatachannelToken = (): void => {\n this.datachannelToken = undefined;\n };\n\n /**\n * Set the handler used to refresh the DataChannel token.\n * @param {function} handler\n * @returns {void}\n */\n public setRefreshHandler = (\n handler: () => Promise<{\n body: {datachannelToken: string; datachannelTokenType: DataChannelTokenType};\n }>\n ): void => {\n this.refreshHandler = handler;\n };\n\n /**\n * Refresh the data channel token using the injected handler.\n * @returns {Promise<object | null>}\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 return await this.refreshHandler();\n } catch (error: any) {\n this.logger.warn(\n `llm#refreshDataChannelToken --> DataChannel token refresh failed: ${\n error?.message || error\n }`\n );\n\n return null;\n }\n }\n\n /**\n * Disconnects the WebSocket and clears all connection state.\n * Overrides Mercury's disconnect to also clear LLM-specific state.\n * Clears connectingPromise first to invalidate any in-flight operations,\n * then clears state before calling super.disconnect(). This ensures:\n * 1. Stale register() responses won't overwrite state (they check promise identity)\n * 2. No deadlock with Mercury's _connectWithBackoff() (we don't await connectingPromise)\n * 3. State is cleared before 'disconnected' event fires\n * @param {object} [options]\n * @returns {Promise<void>}\n */\n public async disconnect(options?: {code: number; reason: string}): Promise<void> {\n // Clear connectingPromise first to invalidate any in-flight operations.\n // Their promise identity checks will detect they've been superseded.\n this.connectingPromise = undefined;\n\n // Clear LLM-specific state before Mercury emits 'disconnected'\n this.webSocketUrl = undefined;\n this.binding = undefined;\n this.locusUrl = undefined;\n this.datachannelUrl = undefined;\n this.datachannelToken = undefined;\n this.refreshHandler = undefined;\n\n await super.disconnect(options);\n }\n\n /**\n * Matches a request URL to a stored datachannel registration URL.\n * Host can differ (e.g. rewritten by hostmap interceptor), so we first\n * try full URL prefix and then fall back to pathname prefix.\n * @param {string} requestUrl\n * @param {string} registrationUrl\n * @returns {boolean}\n */\n public static matchesDatachannelRequestUrl(requestUrl: string, registrationUrl: string): boolean {\n if (!requestUrl || !registrationUrl) {\n return false;\n }\n\n if (requestUrl.startsWith(registrationUrl)) {\n return true;\n }\n\n try {\n const request = new URL(requestUrl);\n const registration = new URL(registrationUrl);\n\n return request.pathname.startsWith(registration.pathname);\n } catch (error) {\n return false;\n }\n }\n\n /**\n * Returns true if the data channel token feature flag is enabled.\n * @returns {Promise<boolean>}\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 * @param {string} baseUrl\n * @param {string[]} subchannels\n * @returns {string}\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/package.json CHANGED
@@ -13,7 +13,7 @@
13
13
  "node": ">=18"
14
14
  },
15
15
  "dependencies": {
16
- "@webex/internal-plugin-mercury": "3.12.0-llmrefactor.4"
16
+ "@webex/internal-plugin-mercury": "3.12.0-llmrefactor.5"
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.12.0-llmrefactor.4"
47
+ "version": "3.12.0-llmrefactor.5"
48
48
  }
package/src/llm.ts CHANGED
@@ -74,9 +74,12 @@ export default class LLMChannel extends (Mercury as any) implements ILLMChannel
74
74
  * Register to the websocket
75
75
  * @param {string} llmSocketUrl
76
76
  * @param {string} datachannelToken
77
- * @returns {Promise<void>}
77
+ * @returns {Promise<object>} Promise resolving to {webSocketUrl, binding}
78
78
  */
79
- private register = async (llmSocketUrl: string, datachannelToken?: string): Promise<void> => {
79
+ private register = async (
80
+ llmSocketUrl: string,
81
+ datachannelToken?: string
82
+ ): Promise<{webSocketUrl: string; binding: string}> => {
80
83
  const isDataChannelTokenEnabled = await this.isDataChannelTokenEnabled();
81
84
 
82
85
  return this.request({
@@ -88,10 +91,7 @@ export default class LLMChannel extends (Mercury as any) implements ILLMChannel
88
91
  ? {'Data-Channel-Auth-Token': datachannelToken}
89
92
  : {},
90
93
  })
91
- .then((res: {body: {webSocketUrl: string; binding: string}}) => {
92
- this.webSocketUrl = res.body.webSocketUrl;
93
- this.binding = res.body.binding;
94
- })
94
+ .then((res: {body: {webSocketUrl: string; binding: string}}) => res.body)
95
95
  .catch((error: any) => {
96
96
  this.logger.error(`Error connecting to websocket for : ${error}`);
97
97
  throw error;
@@ -131,11 +131,27 @@ export default class LLMChannel extends (Mercury as any) implements ILLMChannel
131
131
  this.datachannelUrl = datachannelUrl;
132
132
  const registerStart = performance.now();
133
133
 
134
- this.connectingPromise = this.register(datachannelUrl, datachannelToken)
135
- .then(async () => {
134
+ const connectionPromise: Promise<RegisterAndConnectTiming | void> = this.register(
135
+ datachannelUrl,
136
+ datachannelToken
137
+ )
138
+ .then(async ({webSocketUrl, binding}) => {
139
+ // Check if this operation was invalidated by a disconnect
140
+ if (this.connectingPromise !== connectionPromise) {
141
+ return;
142
+ }
143
+
144
+ this.webSocketUrl = webSocketUrl;
145
+ this.binding = binding;
146
+
136
147
  const clientLLMDatachannelResponseTime = Math.round(performance.now() - registerStart);
137
148
  const isDataChannelTokenEnabled = await this.isDataChannelTokenEnabled();
138
149
 
150
+ // Check again after async operation - disconnect may have run during the await
151
+ if (this.connectingPromise !== connectionPromise) {
152
+ return;
153
+ }
154
+
139
155
  const connectUrl =
140
156
  isDataChannelTokenEnabled && this.webSocketUrl
141
157
  ? LLMChannel.buildUrlWithAwareSubchannels(this.webSocketUrl, AWARE_DATA_CHANNEL)
@@ -158,9 +174,14 @@ export default class LLMChannel extends (Mercury as any) implements ILLMChannel
158
174
  return {clientLLMDatachannelResponseTime, clientLLMWebSocketConnectTime};
159
175
  })
160
176
  .finally(() => {
161
- this.connectingPromise = undefined;
177
+ // Only clear if this operation is still current
178
+ if (this.connectingPromise === connectionPromise) {
179
+ this.connectingPromise = undefined;
180
+ }
162
181
  });
163
182
 
183
+ this.connectingPromise = connectionPromise;
184
+
164
185
  return this.connectingPromise;
165
186
  };
166
187
 
@@ -253,18 +274,28 @@ export default class LLMChannel extends (Mercury as any) implements ILLMChannel
253
274
  /**
254
275
  * Disconnects the WebSocket and clears all connection state.
255
276
  * Overrides Mercury's disconnect to also clear LLM-specific state.
277
+ * Clears connectingPromise first to invalidate any in-flight operations,
278
+ * then clears state before calling super.disconnect(). This ensures:
279
+ * 1. Stale register() responses won't overwrite state (they check promise identity)
280
+ * 2. No deadlock with Mercury's _connectWithBackoff() (we don't await connectingPromise)
281
+ * 3. State is cleared before 'disconnected' event fires
256
282
  * @param {object} [options]
257
283
  * @returns {Promise<void>}
258
284
  */
259
285
  public async disconnect(options?: {code: number; reason: string}): Promise<void> {
260
- await super.disconnect(options);
286
+ // Clear connectingPromise first to invalidate any in-flight operations.
287
+ // Their promise identity checks will detect they've been superseded.
288
+ this.connectingPromise = undefined;
289
+
290
+ // Clear LLM-specific state before Mercury emits 'disconnected'
261
291
  this.webSocketUrl = undefined;
262
292
  this.binding = undefined;
263
293
  this.locusUrl = undefined;
264
294
  this.datachannelUrl = undefined;
265
295
  this.datachannelToken = undefined;
266
296
  this.refreshHandler = undefined;
267
- this.emit('disconnected');
297
+
298
+ await super.disconnect(options);
268
299
  }
269
300
 
270
301
  /**
@@ -25,8 +25,16 @@ describe('plugin-llm', () => {
25
25
  };
26
26
 
27
27
  llmChannel = webex.internal.llm;
28
- // Stub Mercury's prototype disconnect so super.disconnect() works in tests
29
- sinon.stub(Object.getPrototypeOf(Object.getPrototypeOf(llmChannel)), 'disconnect').resolves();
28
+ // Stub Mercury's prototype disconnect so super.disconnect() works in tests.
29
+ // The real Mercury emits 'disconnected' after disconnect completes, so we simulate that.
30
+ sinon
31
+ .stub(Object.getPrototypeOf(Object.getPrototypeOf(llmChannel)), 'disconnect')
32
+ .callsFake(function () {
33
+ this.connected = false;
34
+ this._emit('disconnected');
35
+
36
+ return Promise.resolve();
37
+ });
30
38
  llmChannel.request = sinon.stub().resolves({
31
39
  headers: {},
32
40
  body: {
@@ -347,6 +355,7 @@ describe('plugin-llm', () => {
347
355
  assert.equal(llmChannel.getDatachannelUrl(), undefined);
348
356
  assert.equal(llmChannel.getBinding(), undefined);
349
357
  assert.equal(llmChannel.getDatachannelToken(), undefined);
358
+ assert.equal(llmChannel.isConnecting(), false);
350
359
  });
351
360
 
352
361
  it('works without options', async () => {
@@ -382,6 +391,167 @@ describe('plugin-llm', () => {
382
391
 
383
392
  sinon.assert.calledOnce(disconnectedSpy);
384
393
  });
394
+
395
+ it('clears state before disconnected event fires', async () => {
396
+ await llmChannel.registerAndConnect(locusUrl, datachannelUrl);
397
+ llmChannel.setDatachannelToken('token123');
398
+
399
+ let stateAtEventTime;
400
+
401
+ llmChannel.on('disconnected', () => {
402
+ // Capture state when event fires - should already be cleared
403
+ stateAtEventTime = {
404
+ locusUrl: llmChannel.getLocusUrl(),
405
+ datachannelUrl: llmChannel.getDatachannelUrl(),
406
+ binding: llmChannel.getBinding(),
407
+ datachannelToken: llmChannel.getDatachannelToken(),
408
+ isConnecting: llmChannel.isConnecting(),
409
+ };
410
+ });
411
+
412
+ await llmChannel.disconnect({code: 1000, reason: 'test'});
413
+
414
+ // State should have been undefined when the event fired
415
+ assert.deepEqual(stateAtEventTime, {
416
+ locusUrl: undefined,
417
+ datachannelUrl: undefined,
418
+ binding: undefined,
419
+ datachannelToken: undefined,
420
+ isConnecting: false,
421
+ });
422
+ });
423
+
424
+ it('invalidates in-flight connection via promise identity', async () => {
425
+ let resolveRequest;
426
+ llmChannel.request = sinon.stub().returns(
427
+ new Promise((resolve) => {
428
+ resolveRequest = resolve;
429
+ })
430
+ );
431
+
432
+ // Start connection but don't await - request is pending
433
+ const connectPromise = llmChannel.registerAndConnect(locusUrl, datachannelUrl);
434
+
435
+ assert.equal(llmChannel.isConnecting(), true);
436
+
437
+ // Disconnect while connection is in-flight - should complete immediately
438
+ await llmChannel.disconnect({code: 1000, reason: 'test'});
439
+
440
+ // State should be cleared immediately, not waiting for connection
441
+ assert.equal(llmChannel.getLocusUrl(), undefined);
442
+ assert.equal(llmChannel.isConnecting(), false);
443
+
444
+ // Now resolve the pending request - stale operation should not overwrite state
445
+ resolveRequest({
446
+ headers: {},
447
+ body: {binding: 'stale-binding', webSocketUrl: 'wss://example.com/stale'},
448
+ });
449
+
450
+ // Wait for the stale promise to settle
451
+ await connectPromise;
452
+
453
+ // State should still be undefined - stale operation detected invalidation
454
+ assert.equal(llmChannel.getBinding(), undefined);
455
+ assert.equal(llmChannel.isConnecting(), false);
456
+ });
457
+
458
+ it('stale operation does not clear new connectingPromise', async () => {
459
+ let resolveFirstRequest;
460
+ let resolveSecondRequest;
461
+ llmChannel.request = sinon.stub().returns(
462
+ new Promise((resolve) => {
463
+ resolveFirstRequest = resolve;
464
+ })
465
+ );
466
+
467
+ // Start first connection
468
+ const firstConnectPromise = llmChannel.registerAndConnect(locusUrl, datachannelUrl);
469
+
470
+ // Disconnect invalidates the first connection
471
+ await llmChannel.disconnect({code: 1000, reason: 'test'});
472
+
473
+ // Start second connection before first resolves
474
+ llmChannel.request = sinon.stub().returns(
475
+ new Promise((resolve) => {
476
+ resolveSecondRequest = resolve;
477
+ })
478
+ );
479
+ const secondConnectPromise = llmChannel.registerAndConnect(
480
+ 'newLocusUrl',
481
+ 'newDatachannelUrl'
482
+ );
483
+
484
+ assert.equal(llmChannel.isConnecting(), true);
485
+
486
+ // Resolve the first (stale) request
487
+ resolveFirstRequest({
488
+ headers: {},
489
+ body: {binding: 'stale-binding', webSocketUrl: 'wss://example.com/stale'},
490
+ });
491
+
492
+ await firstConnectPromise;
493
+
494
+ // isConnecting should still be true because second connection is in progress
495
+ assert.equal(llmChannel.isConnecting(), true);
496
+
497
+ // State should not have been overwritten by stale operation
498
+ assert.equal(llmChannel.getBinding(), undefined);
499
+
500
+ // Complete second connection
501
+ resolveSecondRequest({
502
+ headers: {},
503
+ body: {binding: 'new-binding', webSocketUrl: 'wss://example.com/new'},
504
+ });
505
+ await secondConnectPromise;
506
+
507
+ // Now isConnecting should be false and state should reflect second connection
508
+ assert.equal(llmChannel.isConnecting(), false);
509
+ assert.equal(llmChannel.getBinding(), 'new-binding');
510
+ });
511
+
512
+ it('does not call connect() if disconnect runs during isDataChannelTokenEnabled await', async () => {
513
+ let resolveFeatureToggle;
514
+ const featureTogglePromise = new Promise((resolve) => {
515
+ resolveFeatureToggle = resolve;
516
+ });
517
+
518
+ // Request resolves immediately
519
+ llmChannel.request = sinon.stub().resolves({
520
+ headers: {},
521
+ body: {binding: 'binding', webSocketUrl: 'wss://example.com/socket'},
522
+ });
523
+
524
+ // Feature toggle will block
525
+ webex.internal.feature.getFeature = sinon
526
+ .stub()
527
+ .onFirstCall()
528
+ .resolves(true) // First call in register()
529
+ .onSecondCall()
530
+ .returns(featureTogglePromise); // Second call blocks
531
+
532
+ // Start connection
533
+ const connectPromise = llmChannel.registerAndConnect(locusUrl, datachannelUrl);
534
+
535
+ // Wait for register() to complete and state to be set
536
+ await new Promise((resolve) => setImmediate(resolve));
537
+
538
+ // Binding should be set after register() completes
539
+ assert.equal(llmChannel.getBinding(), 'binding');
540
+
541
+ // Disconnect while isDataChannelTokenEnabled() is pending
542
+ await llmChannel.disconnect({code: 1000, reason: 'test'});
543
+
544
+ // State should be cleared
545
+ assert.equal(llmChannel.getBinding(), undefined);
546
+
547
+ // Now resolve the feature toggle - stale operation should NOT call connect()
548
+ resolveFeatureToggle(true);
549
+
550
+ await connectPromise;
551
+
552
+ // connect() should NOT have been called - identity check after await should have aborted
553
+ sinon.assert.notCalled(llmChannel.connect);
554
+ });
385
555
  });
386
556
 
387
557
  describe('#setRefreshHandler', () => {