@webex/internal-plugin-llm 3.12.0-llmrefactor.4 → 3.12.0-llmrefactor.6
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 +81 -50
- package/dist/llm.js.map +1 -1
- package/package.json +2 -2
- package/src/llm.ts +42 -11
- package/test/unit/spec/llm.js +172 -2
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<
|
|
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
|
-
|
|
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
|
-
|
|
146
|
-
var
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
-
|
|
316
|
-
|
|
317
|
-
|
|
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
|
-
|
|
325
|
-
|
|
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(
|
|
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.
|
|
16
|
+
"@webex/internal-plugin-mercury": "3.12.0-llmrefactor.6"
|
|
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.
|
|
47
|
+
"version": "3.12.0-llmrefactor.6"
|
|
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<
|
|
77
|
+
* @returns {Promise<object>} Promise resolving to {webSocketUrl, binding}
|
|
78
78
|
*/
|
|
79
|
-
private register = async (
|
|
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
|
-
|
|
135
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
297
|
+
|
|
298
|
+
await super.disconnect(options);
|
|
268
299
|
}
|
|
269
300
|
|
|
270
301
|
/**
|
package/test/unit/spec/llm.js
CHANGED
|
@@ -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
|
-
|
|
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', () => {
|