@kaltura-sdk/avatars 1.20.0 → 1.21.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/index.esm.js CHANGED
@@ -1,4 +1,4 @@
1
- import { UnisphereLoggerFactory, TinyDataStore, TinyPubSub, performanceMonitor } from '@unisphere/core';
1
+ import { UnisphereLoggerFactory, TinyDataStore, TinyPubSub, performanceMonitor, getOrCreateGlobalTinyDataStore } from '@unisphere/core';
2
2
  import { getPartnerIdFromKs } from '@unisphere/runtime-js';
3
3
  import { DisconnectReasons, getAvatarDefaultCapabilities, resolveActiveContextType, AvatarConnectionStatus } from '@unisphere/genie-types';
4
4
  import { z as z$1 } from 'zod/mini';
@@ -23828,6 +23828,103 @@ const avatarConnectionMachine = setup({
23828
23828
  }
23829
23829
  });
23830
23830
 
23831
+ const ACTIVE_SESSION_STORE_ID = 'agenticExperienceAvatar';
23832
+ function createId() {
23833
+ return typeof crypto !== 'undefined' && crypto.randomUUID ? crypto.randomUUID() : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
23834
+ }
23835
+ /**
23836
+ * Page-wide "who owns the call" record for one avatar manager. Takeover is
23837
+ * just overwriting the shared record: the previous owner sees an id that is
23838
+ * no longer its own and ends its own call (a close callback could not cross
23839
+ * frames).
23840
+ */
23841
+ class ActiveSessionCoordinator {
23842
+ constructor(_options) {
23843
+ this._options = _options;
23844
+ this.id = createId();
23845
+ this._owns = false;
23846
+ this._conflict = new TinyDataStore({
23847
+ initialValue: null,
23848
+ compare: 'deep'
23849
+ });
23850
+ this._win = window;
23851
+ this._onPageHide = () => this.release();
23852
+ this._logger = _options.logger;
23853
+ this._global = getOrCreateGlobalTinyDataStore({
23854
+ id: ACTIVE_SESSION_STORE_ID,
23855
+ initialValue: null,
23856
+ logger: _options.logger
23857
+ });
23858
+ this._unsubscribe = this._global.onChanges(value => this._handle(value));
23859
+ // A closed iframe must not leave the page locked.
23860
+ this._win.addEventListener('pagehide', this._onPageHide);
23861
+ }
23862
+ /** The active session owned by another avatar, or null */
23863
+ get conflict() {
23864
+ return this._conflict.consumer;
23865
+ }
23866
+ claim(preventDisconnections) {
23867
+ var _a, _b;
23868
+ if (this._owns && ((_a = this._global.getData()) === null || _a === void 0 ? void 0 : _a.preventDisconnections) === preventDisconnections) {
23869
+ return;
23870
+ }
23871
+ this._owns = true;
23872
+ (_b = this._logger) === null || _b === void 0 ? void 0 : _b.log('claiming the active avatar session', {
23873
+ data: {
23874
+ id: this.id,
23875
+ preventDisconnections
23876
+ }
23877
+ });
23878
+ this._global.setData(() => ({
23879
+ id: this.id,
23880
+ preventDisconnections
23881
+ }));
23882
+ }
23883
+ /** Republishes the flag only while this avatar owns the call; never claims an idle one */
23884
+ updateFlag(preventDisconnections) {
23885
+ var _a, _b;
23886
+ if (this._owns && ((_a = this._global.getData()) === null || _a === void 0 ? void 0 : _a.id) === this.id && ((_b = this._global.getData()) === null || _b === void 0 ? void 0 : _b.preventDisconnections) !== preventDisconnections) {
23887
+ this._global.setData(() => ({
23888
+ id: this.id,
23889
+ preventDisconnections
23890
+ }));
23891
+ }
23892
+ }
23893
+ /** Clears the record only if it is still ours, so a taken-over avatar can't wipe the new owner */
23894
+ release() {
23895
+ var _a, _b;
23896
+ this._owns = false;
23897
+ if (((_a = this._global.getData()) === null || _a === void 0 ? void 0 : _a.id) === this.id) {
23898
+ (_b = this._logger) === null || _b === void 0 ? void 0 : _b.log('releasing the active avatar session', {
23899
+ data: {
23900
+ id: this.id
23901
+ }
23902
+ });
23903
+ this._global.setData(() => null);
23904
+ }
23905
+ }
23906
+ dispose() {
23907
+ this.release();
23908
+ this._unsubscribe();
23909
+ this._win.removeEventListener('pagehide', this._onPageHide);
23910
+ }
23911
+ _handle(value) {
23912
+ var _a;
23913
+ const other = value && value.id !== this.id ? value : null;
23914
+ this._conflict.setData(() => other);
23915
+ if (other && this._owns) {
23916
+ this._owns = false;
23917
+ (_a = this._logger) === null || _a === void 0 ? void 0 : _a.log('active avatar session taken over by another avatar', {
23918
+ data: {
23919
+ id: this.id,
23920
+ by: other.id
23921
+ }
23922
+ });
23923
+ this._options.onTakenOver();
23924
+ }
23925
+ }
23926
+ }
23927
+
23831
23928
  function setIfChanged(store, next, eq = (a, b) => a === b) {
23832
23929
  if (!eq(store.getData(), next)) {
23833
23930
  store.setData(() => next);
@@ -24108,7 +24205,20 @@ class AvatarClientManager extends ActorManager {
24108
24205
  this._contextSelectorVisible = new TinyDataStore({
24109
24206
  initialValue: true
24110
24207
  });
24208
+ this._multipleAvatarBehavior = new TinyDataStore({
24209
+ initialValue: {
24210
+ preventDisconnections: false
24211
+ },
24212
+ compare: 'deep'
24213
+ });
24214
+ this._isTakeoverPending = new TinyDataStore({
24215
+ initialValue: false
24216
+ });
24111
24217
  this._logger = (_a = this._config.logger) !== null && _a !== void 0 ? _a : avatarClientLoggerFactory.getOrCreateLogger('service', 'avatar-client');
24218
+ this._activeSession = new ActiveSessionCoordinator({
24219
+ onTakenOver: () => this.disconnect(),
24220
+ logger: this._logger
24221
+ });
24112
24222
  if (this._config.runtimeSettings.features) {
24113
24223
  this._features.setData(() => this._config.runtimeSettings.features);
24114
24224
  }
@@ -24121,6 +24231,19 @@ class AvatarClientManager extends ActorManager {
24121
24231
  return this._config.runtimeSettings.serverConfig === 'manual' ? (_a = this._config.runtimeSettings.loadingVideoURL) !== null && _a !== void 0 ? _a : null : null;
24122
24232
  });
24123
24233
  this._setupKalturaContext();
24234
+ this._multipleAvatarBehavior.setData(() => {
24235
+ var _a, _b;
24236
+ return {
24237
+ preventDisconnections: (_b = (_a = this._config.runtimeSettings.multipleAvatarBehavior) === null || _a === void 0 ? void 0 : _a.preventDisconnections) !== null && _b !== void 0 ? _b : false
24238
+ };
24239
+ });
24240
+ this._status.onChanges(status => {
24241
+ if (status === AvatarConnectionStatus.NotConnected || status === AvatarConnectionStatus.Disconnected || status === AvatarConnectionStatus.Error) {
24242
+ this._activeSession.release();
24243
+ }
24244
+ }, {
24245
+ replayLastValue: false
24246
+ });
24124
24247
  }
24125
24248
  // ────────────────────────────────────────────────────────────────────────
24126
24249
  // Private: connection engine
@@ -24327,6 +24450,18 @@ class AvatarClientManager extends ActorManager {
24327
24450
  this._logger.log(`Avatar client already connected`);
24328
24451
  return;
24329
24452
  }
24453
+ const conflict = this._activeSession.conflict.getData();
24454
+ if (conflict) {
24455
+ this._logger.log('Connect pending: another avatar is in a call', {
24456
+ data: {
24457
+ preventDisconnections: conflict.preventDisconnections
24458
+ }
24459
+ });
24460
+ this._pendingTakeoverOptions = options;
24461
+ this._isTakeoverPending.setData(() => true);
24462
+ return;
24463
+ }
24464
+ this._activeSession.claim(this._multipleAvatarBehavior.getData().preventDisconnections);
24330
24465
  const connectionId = 'main';
24331
24466
  this._logger.log(`Connecting to avatar client with connection ID: ${connectionId}`);
24332
24467
  timingTracker.runtimeConnectTime = performance.now();
@@ -24382,6 +24517,22 @@ class AvatarClientManager extends ActorManager {
24382
24517
  reconnect() {
24383
24518
  this.connect(this._lastConnectOptions);
24384
24519
  }
24520
+ confirmTakeover() {
24521
+ var _a;
24522
+ if (!this._isTakeoverPending.getData() || ((_a = this._activeSession.conflict.getData()) === null || _a === void 0 ? void 0 : _a.preventDisconnections)) {
24523
+ return;
24524
+ }
24525
+ const options = this._pendingTakeoverOptions;
24526
+ this._pendingTakeoverOptions = undefined;
24527
+ this._isTakeoverPending.setData(() => false);
24528
+ // Overwriting the page-wide record makes the other avatar end its own call.
24529
+ this._activeSession.claim(this._multipleAvatarBehavior.getData().preventDisconnections);
24530
+ this.connect(options);
24531
+ }
24532
+ cancelTakeover() {
24533
+ this._pendingTakeoverOptions = undefined;
24534
+ this._isTakeoverPending.setData(() => false);
24535
+ }
24385
24536
  // ────────────────────────────────────────────────────────────────────────
24386
24537
  // Public API: pause / resume
24387
24538
  // ────────────────────────────────────────────────────────────────────────
@@ -24584,6 +24735,23 @@ class AvatarClientManager extends ActorManager {
24584
24735
  setIsPreviewMode(value) {
24585
24736
  this._isPreviewMode.setData(() => value);
24586
24737
  }
24738
+ get multipleAvatarBehavior() {
24739
+ return this._multipleAvatarBehavior.consumer;
24740
+ }
24741
+ setMultipleAvatarBehavior(value) {
24742
+ var _a;
24743
+ const preventDisconnections = (_a = value === null || value === void 0 ? void 0 : value.preventDisconnections) !== null && _a !== void 0 ? _a : false;
24744
+ this._multipleAvatarBehavior.setData(() => ({
24745
+ preventDisconnections
24746
+ }));
24747
+ this._activeSession.updateFlag(preventDisconnections);
24748
+ }
24749
+ get activeSessionConflict() {
24750
+ return this._activeSession.conflict;
24751
+ }
24752
+ get isTakeoverPending() {
24753
+ return this._isTakeoverPending.consumer;
24754
+ }
24587
24755
  get contextSelectorVisible() {
24588
24756
  return this._contextSelectorVisible.consumer;
24589
24757
  }
@@ -24661,6 +24829,7 @@ class AvatarClientManager extends ActorManager {
24661
24829
  var _a;
24662
24830
  (_a = this._unsubscribeContextActive) === null || _a === void 0 ? void 0 : _a.call(this);
24663
24831
  this._unsubscribeContextActive = null;
24832
+ this._activeSession.dispose();
24664
24833
  this._cleanupConnection();
24665
24834
  }
24666
24835
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaltura-sdk/avatars",
3
- "version": "1.20.0",
3
+ "version": "1.21.1",
4
4
  "author": "kaltura",
5
5
  "license": "AGPL-3.0",
6
6
  "repository": {
@@ -12,14 +12,15 @@
12
12
  "@kaltura-sdk/rtc-avatar": "^1.33.4",
13
13
  "@kaltura-sdk/rtc-core": "^1.26.2",
14
14
  "@local/genie-shared-utils": "^1.0.0",
15
- "@unisphere/core": "^1.99.0",
16
- "@unisphere/genie-types": "^1.46.0",
15
+ "@unisphere/core": "^1.101.1",
16
+ "@unisphere/genie-types": "^1.47.0",
17
17
  "@unisphere/notifications-core": "^1.25.0",
18
18
  "@unisphere/runtime": "^1.98.0",
19
19
  "@unisphere/runtime-js": "^1.98.0",
20
20
  "immer": "^10.1.1",
21
21
  "js-yaml": "^4.1.0",
22
22
  "socket.io-client": "^4.8.1",
23
+ "vitest": "^3.2.7",
23
24
  "zod": "^4.2.0"
24
25
  },
25
26
  "module": "./index.esm.js",
@@ -1,6 +1,6 @@
1
1
  import { UnisphereLoggerType } from '@unisphere/core';
2
2
  import { KalturaAnalyticsServiceType, PubSubServiceType } from '@unisphere/runtime';
3
- import { AgentData, AgentKalturaContextData, AgentProgressBar, AgentScreenShareInfo, AgentTapToTalkInfo, AvatarClientManagerType, AvatarConnectionProxInternalType, AvatarConnectionProxyType, AvatarConnectionStatus, AvatarContentData, AvatarRuntimeSettings, CaptionConfig, Captions, ChatEntry, ClientToolCallLogEntry, DisconnectReasons, TapToTalkStartOptions } from '@unisphere/genie-types';
3
+ import { AgentData, AgentKalturaContextData, AgentProgressBar, AgentScreenShareInfo, AgentTapToTalkInfo, AvatarClientManagerType, AvatarConnectionProxInternalType, AvatarConnectionProxyType, AvatarConnectionStatus, AvatarContentData, AvatarRuntimeSettings, CaptionConfig, Captions, ChatEntry, ClientToolCallLogEntry, DisconnectReasons, MultipleAvatarBehavior, TapToTalkStartOptions } from '@unisphere/genie-types';
4
4
  import { type AvatarClientToolsInput, type AvatarConnectionActor } from './machines/avatar-connection';
5
5
  import { AvatarClientManagerActions } from './types';
6
6
  import { DeviceType } from '@kaltura-sdk/rtc-core';
@@ -58,6 +58,10 @@ export declare class AvatarClientManager extends ActorManager<AvatarConnectionAc
58
58
  private _features;
59
59
  private readonly _isPreviewMode;
60
60
  private readonly _contextSelectorVisible;
61
+ private readonly _multipleAvatarBehavior;
62
+ private readonly _isTakeoverPending;
63
+ private _pendingTakeoverOptions;
64
+ private readonly _activeSession;
61
65
  constructor(_config: {
62
66
  actions?: AvatarClientManagerActions;
63
67
  debugMode?: boolean;
@@ -113,6 +117,8 @@ export declare class AvatarClientManager extends ActorManager<AvatarConnectionAc
113
117
  }): void;
114
118
  disconnect(): void;
115
119
  reconnect(): void;
120
+ confirmTakeover(): void;
121
+ cancelTakeover(): void;
116
122
  get isPaused(): import("@unisphere/core").TinyDataStoreConsumer<boolean>;
117
123
  pause(): void;
118
124
  resume(): void;
@@ -149,6 +155,10 @@ export declare class AvatarClientManager extends ActorManager<AvatarConnectionAc
149
155
  setExternalThreadId(id: string | null): void;
150
156
  get isPreviewMode(): import("@unisphere/core").TinyDataStoreConsumer<boolean>;
151
157
  setIsPreviewMode(value: boolean): void;
158
+ get multipleAvatarBehavior(): import("@unisphere/core").TinyDataStoreConsumer<MultipleAvatarBehavior>;
159
+ setMultipleAvatarBehavior(value: MultipleAvatarBehavior | undefined): void;
160
+ get activeSessionConflict(): import("@unisphere/core").TinyDataStoreConsumer<import("@unisphere/genie-types").ActiveAvatarSession | null>;
161
+ get isTakeoverPending(): import("@unisphere/core").TinyDataStoreConsumer<boolean>;
152
162
  get contextSelectorVisible(): import("@unisphere/core").TinyDataStoreConsumer<boolean>;
153
163
  setContextSelectorVisible(value: boolean): void;
154
164
  get features(): import("@unisphere/core").TinyDataStoreConsumer<{
@@ -0,0 +1,33 @@
1
+ import { UnisphereLoggerType } from '@unisphere/core';
2
+ import type { ActiveAvatarSession } from '@unisphere/genie-types';
3
+ export declare const ACTIVE_SESSION_STORE_ID = "agenticExperienceAvatar";
4
+ /**
5
+ * Page-wide "who owns the call" record for one avatar manager. Takeover is
6
+ * just overwriting the shared record: the previous owner sees an id that is
7
+ * no longer its own and ends its own call (a close callback could not cross
8
+ * frames).
9
+ */
10
+ export declare class ActiveSessionCoordinator {
11
+ private readonly _options;
12
+ readonly id: string;
13
+ private _owns;
14
+ private readonly _conflict;
15
+ private readonly _global;
16
+ private readonly _unsubscribe;
17
+ private readonly _win;
18
+ private readonly _onPageHide;
19
+ private readonly _logger?;
20
+ constructor(_options: {
21
+ onTakenOver: () => void;
22
+ logger?: UnisphereLoggerType;
23
+ });
24
+ /** The active session owned by another avatar, or null */
25
+ get conflict(): import("@unisphere/core").TinyDataStoreConsumer<ActiveAvatarSession | null>;
26
+ claim(preventDisconnections: boolean): void;
27
+ /** Republishes the flag only while this avatar owns the call; never claims an idle one */
28
+ updateFlag(preventDisconnections: boolean): void;
29
+ /** Clears the record only if it is still ours, so a taken-over avatar can't wipe the new owner */
30
+ release(): void;
31
+ dispose(): void;
32
+ private _handle;
33
+ }
@@ -0,0 +1,11 @@
1
+ import type { ActiveSessionCoordinator } from '../avatar-client-manager/utils/active-session-coordinator';
2
+ type CoordinatorOptions = ConstructorParameters<typeof ActiveSessionCoordinator>[0];
3
+ export declare function getRealTopWindow(): Window;
4
+ export declare function addFrame(parent: Window): Window;
5
+ export declare function installBrowserLikePostMessage(windows: Window[]): void;
6
+ export declare function createFrameApi(win: Window): Promise<{
7
+ createCoordinator: (options: CoordinatorOptions) => ActiveSessionCoordinator;
8
+ cleanup: () => void;
9
+ }>;
10
+ export declare const flush: () => Promise<unknown>;
11
+ export {};