@webex/internal-plugin-llm 3.12.0-next.4 → 3.12.0-next.40

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/src/llm.ts CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  SUBSCRIPTION_AWARE_SUBCHANNELS_PARAM,
11
11
  LLM_DEFAULT_SESSION,
12
12
  } from './constants';
13
- import {ILLMChannel, DataChannelTokenType} from './llm.types';
13
+ import {ILLMChannel, DataChannelTokenType, RegisterAndConnectTiming} from './llm.types';
14
14
 
15
15
  export const config = {
16
16
  llm: {
@@ -51,6 +51,7 @@ export default class LLMChannel extends (Mercury as any) implements ILLMChannel
51
51
  defaultSessionId = LLM_DEFAULT_SESSION;
52
52
  /**
53
53
  * Map to store connection-specific data for multiple LLM connections
54
+ * Key: sessionId
54
55
  * @private
55
56
  * @type {Map<string, {webSocketUrl?: string; binding?: string; locusUrl?: string; datachannelUrl?: string}>}
56
57
  */
@@ -61,19 +62,20 @@ export default class LLMChannel extends (Mercury as any) implements ILLMChannel
61
62
  binding?: string;
62
63
  locusUrl?: string;
63
64
  datachannelUrl?: string;
64
- datachannelToken?: string;
65
+ ownerMeetingId?: string;
66
+ refreshHandler?: () => Promise<{
67
+ body: {datachannelToken: string; datachannelTokenType: DataChannelTokenType};
68
+ }>;
65
69
  }
66
70
  > = new Map();
67
71
 
68
- private datachannelTokens: Record<DataChannelTokenType, string> = {
72
+ // Session-keyed token cache is intentionally decoupled from connection state.
73
+ // Disconnecting a socket session must not implicitly wipe token cache.
74
+ private datachannelTokens: Record<string, string | undefined> = {
69
75
  [DataChannelTokenType.Default]: undefined,
70
76
  [DataChannelTokenType.PracticeSession]: undefined,
71
77
  };
72
78
 
73
- private refreshHandler?: () => Promise<{
74
- body: {datachannelToken: string; datachannelTokenType: DataChannelTokenType};
75
- }>;
76
-
77
79
  /**
78
80
  * Register to the websocket
79
81
  * @param {string} llmSocketUrl
@@ -123,16 +125,36 @@ export default class LLMChannel extends (Mercury as any) implements ILLMChannel
123
125
  datachannelUrl: string,
124
126
  datachannelToken?: string,
125
127
  sessionId: string = LLM_DEFAULT_SESSION
126
- ): Promise<void> =>
127
- this.register(datachannelUrl, datachannelToken, sessionId).then(async () => {
128
- if (!locusUrl || !datachannelUrl) return undefined;
128
+ ): Promise<RegisterAndConnectTiming | undefined> => {
129
+ const registerStart = performance.now();
129
130
 
130
- // Get or create connection data
131
+ // Pre-populate locusUrl and datachannelUrl before register() fires the
132
+ // HTTP POST, so that any token refresh triggered during registration can
133
+ // be routed via connections without falling back to a locusInfo URL scan.
134
+ if (locusUrl && datachannelUrl) {
131
135
  const sessionData = this.connections.get(sessionId) || {};
132
136
  sessionData.locusUrl = locusUrl;
133
137
  sessionData.datachannelUrl = datachannelUrl;
134
- sessionData.datachannelToken = datachannelToken;
135
138
  this.connections.set(sessionId, sessionData);
139
+ }
140
+
141
+ return this.register(datachannelUrl, datachannelToken, sessionId).then(async () => {
142
+ if (!locusUrl || !datachannelUrl) return undefined;
143
+
144
+ const clientLLMDatachannelResponseTime = Math.round(performance.now() - registerStart);
145
+
146
+ // locusUrl and datachannelUrl were pre-populated before register(); here
147
+ // we only need to read the existing session data to get webSocketUrl/binding
148
+ // that register() filled in.
149
+ const sessionData = this.connections.get(sessionId) || {};
150
+
151
+ if (!sessionData.webSocketUrl) {
152
+ // register() succeeded but the response carried no websocket URL. Attach the measured
153
+ // datachannel time so callers don't misreport a registration that completed as time 0.
154
+ const error: any = new Error(`LLM registration for ${sessionId} returned no websocket URL`);
155
+ error.timing = {clientLLMDatachannelResponseTime};
156
+ throw error;
157
+ }
136
158
 
137
159
  const isDataChannelTokenEnabled = await this.isDataChannelTokenEnabled();
138
160
 
@@ -140,8 +162,23 @@ export default class LLMChannel extends (Mercury as any) implements ILLMChannel
140
162
  ? LLMChannel.buildUrlWithAwareSubchannels(sessionData.webSocketUrl, AWARE_DATA_CHANNEL)
141
163
  : sessionData.webSocketUrl;
142
164
 
143
- return this.connect(connectUrl, sessionId);
165
+ const connectStart = performance.now();
166
+
167
+ try {
168
+ await this.connect(connectUrl, sessionId);
169
+ } catch (error) {
170
+ // register() succeeded; only connect() failed. Attach the measured datachannel time so
171
+ // callers don't misreport a websocket failure as a registration that never completed.
172
+ // @ts-ignore
173
+ error.timing = {clientLLMDatachannelResponseTime};
174
+ throw error;
175
+ }
176
+
177
+ const clientLLMWebSocketConnectTime = Math.round(performance.now() - connectStart);
178
+
179
+ return {clientLLMDatachannelResponseTime, clientLLMWebSocketConnectTime};
144
180
  });
181
+ };
145
182
 
146
183
  /**
147
184
  * Tells if LLM socket is connected
@@ -187,73 +224,234 @@ export default class LLMChannel extends (Mercury as any) implements ILLMChannel
187
224
  return sessionData?.datachannelUrl;
188
225
  };
189
226
 
227
+ /**
228
+ * Get WebSocket URL for the connection
229
+ * @param {string} sessionId - Connection identifier
230
+ * @returns {string | undefined} WebSocket URL
231
+ */
232
+ public getWebSocketUrl = (sessionId = LLM_DEFAULT_SESSION): string | undefined => {
233
+ const sessionData = this.connections.get(sessionId);
234
+
235
+ return sessionData?.webSocketUrl;
236
+ };
237
+
238
+ /**
239
+ * Set the owner meeting ID for a given LLM session. Used by the meetings
240
+ * plugin to tag which Meeting instance currently owns the (default) LLM
241
+ * connection so that other Meeting instances can avoid disconnecting or
242
+ * re-initializing a connection they do not own.
243
+ *
244
+ * Does NOT create a connections entry if one does not already exist — this
245
+ * method is a no-op when there is no active session data. Callers should
246
+ * invoke it after a successful `registerAndConnect` or during an explicit
247
+ * ownership handoff.
248
+ *
249
+ * @param {string | undefined} ownerMeetingId - Meeting ID (or undefined to clear)
250
+ * @param {string} sessionId - Connection identifier (defaults to default session)
251
+ * @returns {void}
252
+ */
253
+ public setOwnerMeetingId = (
254
+ ownerMeetingId: string | undefined,
255
+ sessionId: string = LLM_DEFAULT_SESSION
256
+ ): void => {
257
+ const sessionData = this.connections.get(sessionId);
258
+
259
+ if (!sessionData) {
260
+ return;
261
+ }
262
+
263
+ sessionData.ownerMeetingId = ownerMeetingId;
264
+ this.connections.set(sessionId, sessionData);
265
+ };
266
+
267
+ /**
268
+ * Get the owner meeting ID currently associated with an LLM session.
269
+ * Returns undefined when no owner has been assigned (e.g. before the
270
+ * first successful `registerAndConnect`, or after `disconnectLLM`).
271
+ *
272
+ * @param {string} sessionId - Connection identifier (defaults to default session)
273
+ * @returns {string | undefined} ownerMeetingId
274
+ */
275
+ public getOwnerMeetingId = (sessionId: string = LLM_DEFAULT_SESSION): string | undefined => {
276
+ const sessionData = this.connections.get(sessionId);
277
+
278
+ return sessionData?.ownerMeetingId;
279
+ };
280
+
281
+ /**
282
+ * Resolve ownership information for an LLM session.
283
+ *
284
+ * Rules:
285
+ * - no current owner => caller may proceed
286
+ * - caller has no identity to assert => treat as owner
287
+ * - otherwise caller must match current owner
288
+ *
289
+ * @param {string | undefined} ownerMeetingId - Candidate owner to evaluate
290
+ * @param {string} sessionId - Connection identifier (defaults to default session)
291
+ * @returns {{currentOwner: (string|undefined), isOwner: boolean}}
292
+ */
293
+ public resolveSessionOwnership = (
294
+ ownerMeetingId?: string,
295
+ sessionId: string = LLM_DEFAULT_SESSION
296
+ ): {
297
+ currentOwner: string | undefined;
298
+ isOwner: boolean;
299
+ } => {
300
+ const currentOwner = this.getOwnerMeetingId(sessionId);
301
+ const isOwner = !currentOwner || !ownerMeetingId || currentOwner === ownerMeetingId;
302
+
303
+ return {
304
+ currentOwner,
305
+ isOwner,
306
+ };
307
+ };
308
+
190
309
  /**
191
310
  * Get data channel token for the connection
192
- * @param {DataChannelTokenType} dataChannelTokenType
193
- * @returns {string} data channel token
311
+ * @param {DataChannelTokenType|string} tokenKey
312
+ * @param {string | undefined} ownerMeetingId - Meeting id asserting read ownership
313
+ * @returns {string | undefined} data channel token
194
314
  */
195
315
  public getDatachannelToken = (
196
- dataChannelTokenType: DataChannelTokenType = DataChannelTokenType.Default
197
- ): string => {
198
- return this.datachannelTokens[dataChannelTokenType];
316
+ tokenKey?: DataChannelTokenType | string,
317
+ ownerMeetingId?: string
318
+ ): string | undefined => {
319
+ const resolvedTokenKey = tokenKey ?? DataChannelTokenType.Default;
320
+
321
+ const {currentOwner, isOwner} = this.resolveSessionOwnership(ownerMeetingId, resolvedTokenKey);
322
+
323
+ if (!isOwner) {
324
+ this.logger.info(
325
+ `llm#getDatachannelToken --> skip read for session ${resolvedTokenKey}; owned by ${currentOwner}, candidate ${ownerMeetingId}`
326
+ );
327
+
328
+ return undefined;
329
+ }
330
+
331
+ return this.datachannelTokens[resolvedTokenKey];
199
332
  };
200
333
 
201
334
  /**
202
335
  * Set data channel token for the connection
203
336
  * @param {string} datachannelToken - data channel token
204
- * @param {DataChannelTokenType} dataChannelTokenType
337
+ * @param {DataChannelTokenType|string} [tokenKey]
338
+ * @param {string | undefined} ownerMeetingId - Meeting id asserting write ownership
205
339
  * @returns {void}
206
340
  */
207
341
  public setDatachannelToken = (
208
342
  datachannelToken: string,
209
- dataChannelTokenType: DataChannelTokenType = DataChannelTokenType.Default
343
+ tokenKey?: DataChannelTokenType | string,
344
+ ownerMeetingId?: string
210
345
  ): void => {
211
- this.datachannelTokens[dataChannelTokenType] = datachannelToken;
346
+ const resolvedTokenKey = tokenKey ?? DataChannelTokenType.Default;
347
+
348
+ const {currentOwner, isOwner} = this.resolveSessionOwnership(ownerMeetingId, resolvedTokenKey);
349
+
350
+ if (!isOwner) {
351
+ this.logger.info(
352
+ `llm#setDatachannelToken --> skip write for session ${resolvedTokenKey}; owned by ${currentOwner}, candidate ${ownerMeetingId}`
353
+ );
354
+
355
+ return;
356
+ }
357
+
358
+ this.datachannelTokens[resolvedTokenKey] = datachannelToken;
212
359
  };
213
360
 
214
361
  /**
215
- * Resets all data‑channel tokens to their initial undefined values.
216
- * Used when leaving or disconnecting from a meeting.
362
+ * Clears a single session's data channel token.
363
+ * @param {DataChannelTokenType|string} tokenKey
364
+ * @param {string} ownerMeetingId - Meeting id asserting delete ownership
217
365
  * @returns {void}
218
366
  */
219
- public resetDatachannelTokens() {
220
- this.datachannelTokens = {
221
- [DataChannelTokenType.Default]: undefined,
222
- [DataChannelTokenType.PracticeSession]: undefined,
223
- };
224
- }
367
+ public clearDatachannelToken = (
368
+ tokenKey: DataChannelTokenType | string,
369
+ ownerMeetingId: string
370
+ ): void => {
371
+ const resolvedTokenKey = tokenKey;
372
+
373
+ const {currentOwner, isOwner} = this.resolveSessionOwnership(ownerMeetingId, resolvedTokenKey);
374
+
375
+ if (!isOwner) {
376
+ this.logger.info(
377
+ `llm#clearDatachannelToken --> skip clear for session ${resolvedTokenKey}; owned by ${currentOwner}, candidate ${ownerMeetingId}`
378
+ );
379
+
380
+ return;
381
+ }
382
+
383
+ this.datachannelTokens[resolvedTokenKey] = undefined;
384
+ delete this.datachannelTokens[resolvedTokenKey];
385
+ };
225
386
 
226
387
  /**
227
388
  * Set the handler used to refresh the DataChannel token
228
389
  *
229
390
  * @param {function} handler - Function that returns a refreshed token
391
+ * @param {string} [sessionId] - Connection identifier
392
+ * @param {string | undefined} ownerMeetingId - Meeting id asserting refresh-handler ownership
230
393
  * @returns {void}
231
394
  */
232
395
  public setRefreshHandler(
233
396
  handler: () => Promise<{
234
397
  body: {datachannelToken: string; datachannelTokenType: DataChannelTokenType};
235
- }>
398
+ }>,
399
+ sessionId?: string,
400
+ ownerMeetingId?: string
236
401
  ) {
237
- this.refreshHandler = handler;
402
+ const resolvedSessionId = sessionId ?? LLM_DEFAULT_SESSION;
403
+
404
+ const {currentOwner, isOwner} = this.resolveSessionOwnership(ownerMeetingId, resolvedSessionId);
405
+
406
+ if (!isOwner) {
407
+ this.logger.info(
408
+ `llm#setRefreshHandler --> skip write for session ${resolvedSessionId}; owned by ${currentOwner}, candidate ${ownerMeetingId}`
409
+ );
410
+
411
+ return;
412
+ }
413
+
414
+ const sessionData = this.connections.get(resolvedSessionId);
415
+
416
+ if (sessionData) {
417
+ sessionData.refreshHandler = handler;
418
+ if (ownerMeetingId) {
419
+ sessionData.ownerMeetingId = ownerMeetingId;
420
+ }
421
+
422
+ return;
423
+ }
424
+
425
+ // Intentionally allow a pre-connection session shape here.
426
+ // Some flows inject refreshHandler before register/connect so token refresh
427
+ // is already wired when the socket lifecycle starts. register()/
428
+ // registerAndConnect() will later fill webSocketUrl/binding/locusUrl/
429
+ // datachannelUrl into this same session entry.
430
+ this.connections.set(resolvedSessionId, {
431
+ refreshHandler: handler,
432
+ ownerMeetingId,
433
+ });
238
434
  }
239
435
 
240
436
  /**
241
437
  * Refresh the data channel token using the injected handler.
242
438
  * Logs a descriptive error if the handler is missing or fails.
243
- *
439
+ * @param {string} sessionId - Connection identifier (defaults to default session)
244
440
  * @returns {Promise<string>} The refreshed token.
245
441
  */
246
- public async refreshDataChannelToken() {
247
- if (!this.refreshHandler) {
442
+ public async refreshDataChannelToken(sessionId: string = LLM_DEFAULT_SESSION) {
443
+ const refreshHandler = this.connections.get(sessionId)?.refreshHandler;
444
+
445
+ if (!refreshHandler) {
248
446
  this.logger.warn(
249
- 'llm#refreshDataChannelToken --> LLM refreshHandler is not set, skipping token refresh'
447
+ `llm#refreshDataChannelToken --> LLM refreshHandler is not set for session ${sessionId}, skipping token refresh`
250
448
  );
251
449
 
252
450
  return null;
253
451
  }
254
452
 
255
453
  try {
256
- const res = await this.refreshHandler();
454
+ const res = await refreshHandler();
257
455
 
258
456
  return res;
259
457
  } catch (error: any) {
@@ -271,16 +469,51 @@ export default class LLMChannel extends (Mercury as any) implements ILLMChannel
271
469
  * Disconnects websocket connection
272
470
  * @param {{code: number, reason: string}} options - The disconnect option object with code and reason
273
471
  * @param {string} sessionId - Connection identifier
274
- * @returns {Promise<void>}
472
+ * @param {string} ownerMeetingId - Meeting id asserting disconnect ownership
473
+ * @returns {Promise<boolean>} True when disconnect was performed, false when skipped
275
474
  */
276
475
  public disconnectLLM = (
277
476
  options: {code: number; reason: string},
278
- sessionId: string = LLM_DEFAULT_SESSION
279
- ): Promise<void> =>
280
- this.disconnect(options, sessionId).then(() => {
477
+ sessionId?: string,
478
+ ownerMeetingId?: string
479
+ ): Promise<boolean> => {
480
+ const resolvedSessionId = sessionId ?? LLM_DEFAULT_SESSION;
481
+
482
+ // Backward-compat path: historically callers could omit ownerMeetingId
483
+ // (and sometimes sessionId). Reuse current owner when available so legacy
484
+ // calls remain best-effort without throwing at teardown time.
485
+ const resolvedOwnerMeetingId = ownerMeetingId || this.getOwnerMeetingId(resolvedSessionId);
486
+
487
+ if (!ownerMeetingId) {
488
+ this.logger.warn(
489
+ `llm#disconnectLLM --> ownerMeetingId is omitted for session ${resolvedSessionId}; using legacy compatibility path`
490
+ );
491
+ }
492
+
493
+ const {currentOwner, isOwner} = this.resolveSessionOwnership(
494
+ resolvedOwnerMeetingId,
495
+ resolvedSessionId
496
+ );
497
+
498
+ if (!isOwner) {
499
+ this.logger.info(
500
+ `llm#disconnectLLM --> skip disconnect for session ${resolvedSessionId}; owned by ${currentOwner}, candidate ${resolvedOwnerMeetingId}`
501
+ );
502
+
503
+ return Promise.resolve(false);
504
+ }
505
+
506
+ return this.disconnect(options, resolvedSessionId).then(() => {
507
+ // Clear owner tag before cleanup to ensure it's not lingering
508
+ // if another meeting claimed it during disconnect
509
+ this.setOwnerMeetingId(undefined, resolvedSessionId);
510
+
281
511
  // Clean up sessions data
282
- this.connections.delete(sessionId);
512
+ this.connections.delete(resolvedSessionId);
513
+
514
+ return true;
283
515
  });
516
+ };
284
517
 
285
518
  /**
286
519
  * Disconnects all LLM websocket connections
@@ -304,10 +537,79 @@ export default class LLMChannel extends (Mercury as any) implements ILLMChannel
304
537
  binding?: string;
305
538
  locusUrl?: string;
306
539
  datachannelUrl?: string;
307
- datachannelToken?: string;
540
+ ownerMeetingId?: string;
308
541
  }
309
542
  > => new Map(this.connections);
310
543
 
544
+ /**
545
+ * Look up the locusUrl associated with a datachannel request URL.
546
+ * Iterates all active LLM sessions and returns the locusUrl of the
547
+ * session whose stored datachannelUrl is a prefix of the given request URL.
548
+ *
549
+ * @param {string} requestUrl - The in-flight request URL to match
550
+ * @returns {string | undefined} The matching locusUrl, or undefined if not found
551
+ */
552
+ public getLocusUrlByDatachannelUrl(requestUrl: string): string | undefined {
553
+ for (const [, connection] of this.connections) {
554
+ if (
555
+ connection.datachannelUrl &&
556
+ LLMChannel.matchesDatachannelRequestUrl(requestUrl, connection.datachannelUrl)
557
+ ) {
558
+ return connection.locusUrl;
559
+ }
560
+ }
561
+
562
+ return undefined;
563
+ }
564
+
565
+ /**
566
+ * Look up the sessionId associated with a datachannel request URL.
567
+ * Iterates all active LLM sessions and returns the sessionId whose
568
+ * stored datachannelUrl is a prefix of the given request URL.
569
+ *
570
+ * @param {string} requestUrl - The in-flight request URL to match
571
+ * @returns {string | undefined} The matching sessionId, or undefined if not found
572
+ */
573
+ public getSessionIdByDatachannelUrl(requestUrl: string): string | undefined {
574
+ for (const [sessionId, connection] of this.connections) {
575
+ if (
576
+ connection.datachannelUrl &&
577
+ LLMChannel.matchesDatachannelRequestUrl(requestUrl, connection.datachannelUrl)
578
+ ) {
579
+ return sessionId;
580
+ }
581
+ }
582
+
583
+ return undefined;
584
+ }
585
+
586
+ /**
587
+ * Matches a request URL to a stored datachannel registration URL.
588
+ * Host can differ (e.g. rewritten by hostmap interceptor), so we first
589
+ * try full URL prefix and then fall back to pathname prefix.
590
+ * @param {string} requestUrl
591
+ * @param {string} registrationUrl
592
+ * @returns {boolean}
593
+ */
594
+ public static matchesDatachannelRequestUrl(requestUrl: string, registrationUrl: string): boolean {
595
+ if (!requestUrl || !registrationUrl) {
596
+ return false;
597
+ }
598
+
599
+ if (requestUrl.startsWith(registrationUrl)) {
600
+ return true;
601
+ }
602
+
603
+ try {
604
+ const request = new URL(requestUrl);
605
+ const registration = new URL(registrationUrl);
606
+
607
+ return request.pathname.startsWith(registration.pathname);
608
+ } catch (error) {
609
+ return false;
610
+ }
611
+ }
612
+
311
613
  /**
312
614
  * Returns true if data channel token is enabled, false otherwise
313
615
  * @returns {Promise<boolean>} resolves with true if data channel token is enabled
package/src/llm.types.ts CHANGED
@@ -1,16 +1,67 @@
1
+ export enum DataChannelTokenType {
2
+ Default = 'llm-default-session',
3
+ PracticeSession = 'llm-practice-session',
4
+ }
5
+
6
+ type DataChannelTokenKey = DataChannelTokenType | string;
7
+
8
+ /**
9
+ * Latencies (in milliseconds) captured during register + websocket connect.
10
+ */
11
+ type RegisterAndConnectTiming = {
12
+ clientLLMDatachannelResponseTime?: number;
13
+ clientLLMWebSocketConnectTime?: number;
14
+ };
15
+
1
16
  interface ILLMChannel {
2
17
  registerAndConnect: (
3
18
  locusUrl: string,
4
19
  datachannelUrl: string,
5
20
  datachannelToken?: string,
6
21
  sessionId?: string
7
- ) => Promise<void>;
22
+ ) => Promise<RegisterAndConnectTiming | undefined>;
8
23
  isConnected: (sessionId?: string) => boolean;
9
24
  getBinding: (sessionId?: string) => string;
10
25
  getLocusUrl: (sessionId?: string) => string;
11
26
  getDatachannelUrl: (sessionId?: string) => string;
12
- disconnectLLM: (options: {code: number; reason: string}, sessionId?: string) => Promise<void>;
27
+ getWebSocketUrl: (sessionId?: string) => string | undefined;
28
+ disconnectLLM: (
29
+ options: {code: number; reason: string},
30
+ sessionId?: string,
31
+ ownerMeetingId?: string
32
+ ) => Promise<boolean>;
13
33
  disconnectAllLLM: (options?: {code: number; reason: string}) => Promise<void>;
34
+ setOwnerMeetingId: (ownerMeetingId: string | undefined, sessionId?: string) => void;
35
+ getOwnerMeetingId: (sessionId?: string) => string | undefined;
36
+ resolveSessionOwnership: (
37
+ ownerMeetingId?: string,
38
+ sessionId?: string
39
+ ) => {
40
+ currentOwner: string | undefined;
41
+ isOwner: boolean;
42
+ };
43
+ getDatachannelToken: (
44
+ tokenKey?: DataChannelTokenKey,
45
+ ownerMeetingId?: string
46
+ ) => string | undefined;
47
+ setDatachannelToken: (
48
+ datachannelToken: string,
49
+ tokenKey?: DataChannelTokenKey,
50
+ ownerMeetingId?: string
51
+ ) => void;
52
+ clearDatachannelToken: (tokenKey: DataChannelTokenKey, ownerMeetingId: string) => void;
53
+ setRefreshHandler: (
54
+ handler: () => Promise<{
55
+ body: {datachannelToken: string; datachannelTokenType: DataChannelTokenType};
56
+ }>,
57
+ sessionId?: string,
58
+ ownerMeetingId?: string
59
+ ) => void;
60
+ refreshDataChannelToken: (sessionId?: string) => Promise<{
61
+ body: {datachannelToken: string; datachannelTokenType: DataChannelTokenType};
62
+ } | null>;
63
+ getLocusUrlByDatachannelUrl: (requestUrl: string) => string | undefined;
64
+ getSessionIdByDatachannelUrl: (requestUrl: string) => string | undefined;
14
65
  getAllConnections: () => Map<
15
66
  string,
16
67
  {
@@ -18,15 +69,10 @@ interface ILLMChannel {
18
69
  binding?: string;
19
70
  locusUrl?: string;
20
71
  datachannelUrl?: string;
21
- datachannelToken?: string;
72
+ ownerMeetingId?: string;
22
73
  }
23
74
  >;
24
75
  }
25
76
 
26
- export enum DataChannelTokenType {
27
- Default = 'llm-default-session',
28
- PracticeSession = 'llm-practice-session',
29
- }
30
-
31
77
  // eslint-disable-next-line import/prefer-default-export
32
- export type {ILLMChannel};
78
+ export type {ILLMChannel, DataChannelTokenKey, RegisterAndConnectTiming};