@webex/internal-plugin-llm 3.12.0-task-refactor.1 → 3.12.0-webex-services-ready.1

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