@kaltura-sdk/avatars 1.19.0 → 1.21.0
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/README.md +1 -1
- package/index.esm.js +431 -4
- package/package.json +3 -2
- package/src/index.d.ts +1 -0
- package/src/lib/avatar-client-manager/avatar-client-manager.d.ts +11 -1
- package/src/lib/avatar-client-manager/utils/active-session-coordinator.d.ts +33 -0
- package/src/lib/global-tiny-data-store/index.d.ts +1 -0
- package/src/lib/global-tiny-data-store/listen-to-global-tiny-data-store.d.ts +16 -0
- package/src/lib/test-utils/create-frame-api.d.ts +17 -0
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@ manager.stop(); // full teardown, unsubscribes everything
|
|
|
34
34
|
- **Pause/resume**: `pause()`, `resume()`, `pauseAfterSpeaking()`, `isPaused`
|
|
35
35
|
- **Chat & content**: `askViaText()`, `chatLog`, `content`, `setLocalContent()`, `clearContent()`, `agentData`
|
|
36
36
|
- **Screen share**: `startScreenShare()`, `stopScreenShare()`, `screenShareInfo`
|
|
37
|
-
- **Tap-to-talk**: `tapToTalkStart(options?)` (`options.enrichAudio` asks the server to analyze the recording), `tapToTalkEnd()`, `toggleTapToTalkMode()`, `tapToTalkInfo`
|
|
37
|
+
- **Tap-to-talk**: `tapToTalkStart(options?)` (`options.enrichAudio` asks the server to analyze the recording; `options.referenceText` is the text being read, sent with `enrichAudio`), `tapToTalkEnd()`, `toggleTapToTalkMode()`, `tapToTalkInfo`
|
|
38
38
|
- **Captions**: `captions` (state + `toggle`/`setReplacements`/`setFilter`)
|
|
39
39
|
- **Kaltura context**: `kalturaContext`, `toggleKalturaContext()`, `updateDynamicVariables()`
|
|
40
40
|
- **Client tools**: `registerClientTools()`, `unregisterClientTools()`, `registeredClientTools`, `clientToolCallLog`
|
package/index.esm.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { UnisphereLoggerFactory, TinyDataStore, TinyPubSub, performanceMonitor } from '@unisphere/core';
|
|
1
|
+
import { UnisphereLoggerFactory, TinyDataStore, TinyPubSub, performanceMonitor, getOrCreateGlobalUnisphere } 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';
|
|
@@ -1920,6 +1920,18 @@ PERFORMANCE OF THIS SOFTWARE.
|
|
|
1920
1920
|
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
|
|
1921
1921
|
|
|
1922
1922
|
|
|
1923
|
+
function __rest(s, e) {
|
|
1924
|
+
var t = {};
|
|
1925
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
1926
|
+
t[p] = s[p];
|
|
1927
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
1928
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
1929
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
1930
|
+
t[p[i]] = s[p[i]];
|
|
1931
|
+
}
|
|
1932
|
+
return t;
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1923
1935
|
function __awaiter(thisArg, _arguments, P, generator) {
|
|
1924
1936
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
1925
1937
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
@@ -23112,14 +23124,20 @@ const sendTapToTalkStart = ({
|
|
|
23112
23124
|
if (!client) {
|
|
23113
23125
|
throw new Error('WebSocket client not found');
|
|
23114
23126
|
}
|
|
23115
|
-
const
|
|
23127
|
+
const {
|
|
23128
|
+
enrichAudio,
|
|
23129
|
+
referenceText
|
|
23130
|
+
} = (_a = event.options) !== null && _a !== void 0 ? _a : {};
|
|
23116
23131
|
// No payload without options keeps the event identical for servers that predate the flag
|
|
23117
23132
|
if (enrichAudio === undefined) {
|
|
23118
23133
|
client.emit('tapToTalkStart');
|
|
23119
23134
|
return;
|
|
23120
23135
|
}
|
|
23121
|
-
client.emit('tapToTalkStart', {
|
|
23136
|
+
client.emit('tapToTalkStart', referenceText === undefined ? {
|
|
23122
23137
|
enrichAudio
|
|
23138
|
+
} : {
|
|
23139
|
+
enrichAudio,
|
|
23140
|
+
referenceText
|
|
23123
23141
|
});
|
|
23124
23142
|
};
|
|
23125
23143
|
|
|
@@ -23822,6 +23840,343 @@ const avatarConnectionMachine = setup({
|
|
|
23822
23840
|
}
|
|
23823
23841
|
});
|
|
23824
23842
|
|
|
23843
|
+
const NAMESPACE = 'unisphere-global-tiny-data-store';
|
|
23844
|
+
const REGISTRY_KEY = 'globalTinyDataStore';
|
|
23845
|
+
function getRegistry(logger) {
|
|
23846
|
+
return getOrCreateGlobalUnisphere(REGISTRY_KEY, logger, {
|
|
23847
|
+
windowId: `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`,
|
|
23848
|
+
// null prototype: ids come off the wire, `__proto__`/`constructor` must be plain keys
|
|
23849
|
+
entries: Object.create(null),
|
|
23850
|
+
listening: false,
|
|
23851
|
+
lastTs: 0
|
|
23852
|
+
}).value;
|
|
23853
|
+
}
|
|
23854
|
+
// `length`/`frames` are readable cross-origin, so this reaches every frame
|
|
23855
|
+
// regardless of nesting depth or origin; we can't know which one holds an avatar.
|
|
23856
|
+
function collectWindows(root) {
|
|
23857
|
+
const result = [];
|
|
23858
|
+
const visit = w => {
|
|
23859
|
+
result.push(w);
|
|
23860
|
+
let count = 0;
|
|
23861
|
+
try {
|
|
23862
|
+
count = w.length;
|
|
23863
|
+
} catch (_a) {
|
|
23864
|
+
return;
|
|
23865
|
+
}
|
|
23866
|
+
for (let i = 0; i < count; i++) {
|
|
23867
|
+
try {
|
|
23868
|
+
visit(w.frames[i]);
|
|
23869
|
+
} catch (_b) {
|
|
23870
|
+
// frame navigated away or became inaccessible while enumerating
|
|
23871
|
+
}
|
|
23872
|
+
}
|
|
23873
|
+
};
|
|
23874
|
+
visit(root);
|
|
23875
|
+
return result;
|
|
23876
|
+
}
|
|
23877
|
+
// Cache frames seen by this window so a `pagehide` message dispatched by an iframe
|
|
23878
|
+
// just before removal is still recognized even after `contentWindow` detaches from the DOM.
|
|
23879
|
+
const knownFrames = new WeakSet();
|
|
23880
|
+
function getPageWindows(win) {
|
|
23881
|
+
var _a;
|
|
23882
|
+
const current = collectWindows((_a = win.top) !== null && _a !== void 0 ? _a : win);
|
|
23883
|
+
for (const w of current) {
|
|
23884
|
+
knownFrames.add(w);
|
|
23885
|
+
}
|
|
23886
|
+
return current;
|
|
23887
|
+
}
|
|
23888
|
+
function isKnownPageWindow(win, source) {
|
|
23889
|
+
if (getPageWindows(win).includes(source)) {
|
|
23890
|
+
return true;
|
|
23891
|
+
}
|
|
23892
|
+
return knownFrames.has(source);
|
|
23893
|
+
}
|
|
23894
|
+
function post(target, message) {
|
|
23895
|
+
try {
|
|
23896
|
+
// '*' because the host topology (and origins) are unknown. The payload is
|
|
23897
|
+
// non-sensitive and receivers only accept messages from this page's frames.
|
|
23898
|
+
target.postMessage(message, '*');
|
|
23899
|
+
} catch (_a) {
|
|
23900
|
+
// target is gone
|
|
23901
|
+
}
|
|
23902
|
+
}
|
|
23903
|
+
function broadcast(win, message) {
|
|
23904
|
+
for (const target of getPageWindows(win)) {
|
|
23905
|
+
if (target !== win) {
|
|
23906
|
+
post(target, message);
|
|
23907
|
+
}
|
|
23908
|
+
}
|
|
23909
|
+
}
|
|
23910
|
+
function compareRevisions(a, b) {
|
|
23911
|
+
if (a.ts !== b.ts) {
|
|
23912
|
+
return a.ts - b.ts;
|
|
23913
|
+
}
|
|
23914
|
+
return a.origin < b.origin ? -1 : a.origin > b.origin ? 1 : 0;
|
|
23915
|
+
}
|
|
23916
|
+
function isRevision(rev) {
|
|
23917
|
+
return !!rev && typeof rev.ts === 'number' && typeof rev.origin === 'string';
|
|
23918
|
+
}
|
|
23919
|
+
function handleMessage(win, registry, logger, event) {
|
|
23920
|
+
const data = event.data;
|
|
23921
|
+
if (!data || data.ns !== NAMESPACE || typeof data.id !== 'string') {
|
|
23922
|
+
return;
|
|
23923
|
+
}
|
|
23924
|
+
const source = event.source;
|
|
23925
|
+
if (!source || source === win || !isKnownPageWindow(win, source)) {
|
|
23926
|
+
logger.debug('ignored message from a window outside this page', {
|
|
23927
|
+
data: {
|
|
23928
|
+
id: data.id,
|
|
23929
|
+
kind: data.kind
|
|
23930
|
+
}
|
|
23931
|
+
});
|
|
23932
|
+
return;
|
|
23933
|
+
}
|
|
23934
|
+
const entry = registry.entries[data.id];
|
|
23935
|
+
if (data.kind === 'request-sync') {
|
|
23936
|
+
if (entry === null || entry === void 0 ? void 0 : entry.rev) {
|
|
23937
|
+
logger.debug('answering sync request', {
|
|
23938
|
+
data: {
|
|
23939
|
+
id: data.id
|
|
23940
|
+
}
|
|
23941
|
+
});
|
|
23942
|
+
post(source, {
|
|
23943
|
+
ns: NAMESPACE,
|
|
23944
|
+
from: registry.windowId,
|
|
23945
|
+
kind: 'update',
|
|
23946
|
+
id: data.id,
|
|
23947
|
+
value: entry.store.getData(),
|
|
23948
|
+
rev: entry.rev
|
|
23949
|
+
});
|
|
23950
|
+
}
|
|
23951
|
+
return;
|
|
23952
|
+
}
|
|
23953
|
+
if (data.kind !== 'update' || !isRevision(data.rev)) {
|
|
23954
|
+
return;
|
|
23955
|
+
}
|
|
23956
|
+
// Lamport-style: a write made after observing this one must sort after it,
|
|
23957
|
+
// even within the same millisecond.
|
|
23958
|
+
registry.lastTs = Math.max(registry.lastTs, data.rev.ts);
|
|
23959
|
+
if (!entry) {
|
|
23960
|
+
logger.debug('stored update for an id nobody here listens to yet', {
|
|
23961
|
+
data: {
|
|
23962
|
+
id: data.id,
|
|
23963
|
+
rev: data.rev
|
|
23964
|
+
}
|
|
23965
|
+
});
|
|
23966
|
+
registry.entries[data.id] = {
|
|
23967
|
+
store: new TinyDataStore({
|
|
23968
|
+
initialValue: data.value,
|
|
23969
|
+
compare: 'deep'
|
|
23970
|
+
}),
|
|
23971
|
+
rev: data.rev
|
|
23972
|
+
};
|
|
23973
|
+
return;
|
|
23974
|
+
}
|
|
23975
|
+
// Equal/older revisions are what stops echoes between frames.
|
|
23976
|
+
if (entry.rev && compareRevisions(data.rev, entry.rev) <= 0) {
|
|
23977
|
+
logger.debug('dropped stale or duplicate update', {
|
|
23978
|
+
data: {
|
|
23979
|
+
id: data.id,
|
|
23980
|
+
rev: data.rev
|
|
23981
|
+
}
|
|
23982
|
+
});
|
|
23983
|
+
return;
|
|
23984
|
+
}
|
|
23985
|
+
logger.debug('applied update from another frame', {
|
|
23986
|
+
data: {
|
|
23987
|
+
id: data.id,
|
|
23988
|
+
value: data.value,
|
|
23989
|
+
rev: data.rev
|
|
23990
|
+
}
|
|
23991
|
+
});
|
|
23992
|
+
entry.rev = data.rev;
|
|
23993
|
+
entry.store.setData(() => data.value);
|
|
23994
|
+
}
|
|
23995
|
+
// The listener is installed once per window by whichever bundle listens first,
|
|
23996
|
+
// so it keeps that bundle's logger.
|
|
23997
|
+
function ensureListener(win, registry, logger) {
|
|
23998
|
+
if (registry.listening) {
|
|
23999
|
+
return;
|
|
24000
|
+
}
|
|
24001
|
+
registry.listening = true;
|
|
24002
|
+
win.addEventListener('message', event => handleMessage(win, registry, logger, event));
|
|
24003
|
+
}
|
|
24004
|
+
/**
|
|
24005
|
+
* A `TinyDataStore` shared by id between every workspace in this window and,
|
|
24006
|
+
* via `postMessage`, every other frame on the page (parent, siblings, nested).
|
|
24007
|
+
* Last write wins (timestamp, then window id as tie-break).
|
|
24008
|
+
*/
|
|
24009
|
+
function listenToGlobalTinyDataStore({
|
|
24010
|
+
id,
|
|
24011
|
+
initialValue,
|
|
24012
|
+
onChanges,
|
|
24013
|
+
logger = avatarClientLoggerFactory.getOrCreateLogger('service', 'global-tiny-data-store')
|
|
24014
|
+
}) {
|
|
24015
|
+
const win = window;
|
|
24016
|
+
const registry = getRegistry(logger);
|
|
24017
|
+
ensureListener(win, registry, logger);
|
|
24018
|
+
let entry = registry.entries[id];
|
|
24019
|
+
const isNew = !entry;
|
|
24020
|
+
if (!entry) {
|
|
24021
|
+
entry = registry.entries[id] = {
|
|
24022
|
+
store: new TinyDataStore({
|
|
24023
|
+
initialValue,
|
|
24024
|
+
compare: 'deep'
|
|
24025
|
+
}),
|
|
24026
|
+
rev: null
|
|
24027
|
+
};
|
|
24028
|
+
}
|
|
24029
|
+
const store = entry.store;
|
|
24030
|
+
const unsubscribe = store.onChanges(onChanges, {
|
|
24031
|
+
replayLastValue: false
|
|
24032
|
+
});
|
|
24033
|
+
logger.debug('listening to global store', {
|
|
24034
|
+
data: {
|
|
24035
|
+
id,
|
|
24036
|
+
isNew
|
|
24037
|
+
}
|
|
24038
|
+
});
|
|
24039
|
+
if (isNew) {
|
|
24040
|
+
broadcast(win, {
|
|
24041
|
+
ns: NAMESPACE,
|
|
24042
|
+
from: registry.windowId,
|
|
24043
|
+
kind: 'request-sync',
|
|
24044
|
+
id
|
|
24045
|
+
});
|
|
24046
|
+
}
|
|
24047
|
+
return {
|
|
24048
|
+
unsubscribe,
|
|
24049
|
+
getData: () => store.getData(),
|
|
24050
|
+
setData: value => {
|
|
24051
|
+
const rev = {
|
|
24052
|
+
ts: registry.lastTs = Math.max(Date.now(), registry.lastTs + 1),
|
|
24053
|
+
origin: registry.windowId
|
|
24054
|
+
};
|
|
24055
|
+
logger.debug('updating global store', {
|
|
24056
|
+
data: {
|
|
24057
|
+
id,
|
|
24058
|
+
value,
|
|
24059
|
+
rev
|
|
24060
|
+
}
|
|
24061
|
+
});
|
|
24062
|
+
entry.rev = rev;
|
|
24063
|
+
store.setData(() => value);
|
|
24064
|
+
broadcast(win, {
|
|
24065
|
+
ns: NAMESPACE,
|
|
24066
|
+
from: registry.windowId,
|
|
24067
|
+
kind: 'update',
|
|
24068
|
+
id,
|
|
24069
|
+
value,
|
|
24070
|
+
rev
|
|
24071
|
+
});
|
|
24072
|
+
}
|
|
24073
|
+
};
|
|
24074
|
+
}
|
|
24075
|
+
|
|
24076
|
+
const ACTIVE_SESSION_STORE_ID = 'agenticExperienceAvatar';
|
|
24077
|
+
function createId() {
|
|
24078
|
+
return typeof crypto !== 'undefined' && crypto.randomUUID ? crypto.randomUUID() : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
24079
|
+
}
|
|
24080
|
+
/**
|
|
24081
|
+
* Page-wide "who owns the call" record for one avatar manager. Takeover is
|
|
24082
|
+
* just overwriting the shared record: the previous owner sees an id that is
|
|
24083
|
+
* no longer its own and ends its own call (a close callback could not cross
|
|
24084
|
+
* frames).
|
|
24085
|
+
*/
|
|
24086
|
+
class ActiveSessionCoordinator {
|
|
24087
|
+
constructor(_options) {
|
|
24088
|
+
this._options = _options;
|
|
24089
|
+
this.id = createId();
|
|
24090
|
+
this._owns = false;
|
|
24091
|
+
this._conflict = new TinyDataStore({
|
|
24092
|
+
initialValue: null,
|
|
24093
|
+
compare: 'deep'
|
|
24094
|
+
});
|
|
24095
|
+
this._win = window;
|
|
24096
|
+
this._onPageHide = () => this.release();
|
|
24097
|
+
const _a = listenToGlobalTinyDataStore({
|
|
24098
|
+
id: ACTIVE_SESSION_STORE_ID,
|
|
24099
|
+
initialValue: null,
|
|
24100
|
+
onChanges: value => this._handle(value),
|
|
24101
|
+
logger: _options.logger
|
|
24102
|
+
}),
|
|
24103
|
+
{
|
|
24104
|
+
unsubscribe
|
|
24105
|
+
} = _a,
|
|
24106
|
+
global = __rest(_a, ["unsubscribe"]);
|
|
24107
|
+
this._logger = _options.logger;
|
|
24108
|
+
this._global = global;
|
|
24109
|
+
this._unsubscribe = unsubscribe;
|
|
24110
|
+
this._handle(global.getData());
|
|
24111
|
+
// A closed iframe must not leave the page locked.
|
|
24112
|
+
this._win.addEventListener('pagehide', this._onPageHide);
|
|
24113
|
+
}
|
|
24114
|
+
/** The active session owned by another avatar, or null */
|
|
24115
|
+
get conflict() {
|
|
24116
|
+
return this._conflict.consumer;
|
|
24117
|
+
}
|
|
24118
|
+
claim(preventDisconnections) {
|
|
24119
|
+
var _a, _b;
|
|
24120
|
+
if (this._owns && ((_a = this._global.getData()) === null || _a === void 0 ? void 0 : _a.preventDisconnections) === preventDisconnections) {
|
|
24121
|
+
return;
|
|
24122
|
+
}
|
|
24123
|
+
this._owns = true;
|
|
24124
|
+
(_b = this._logger) === null || _b === void 0 ? void 0 : _b.log('claiming the active avatar session', {
|
|
24125
|
+
data: {
|
|
24126
|
+
id: this.id,
|
|
24127
|
+
preventDisconnections
|
|
24128
|
+
}
|
|
24129
|
+
});
|
|
24130
|
+
this._global.setData({
|
|
24131
|
+
id: this.id,
|
|
24132
|
+
preventDisconnections
|
|
24133
|
+
});
|
|
24134
|
+
}
|
|
24135
|
+
/** Republishes the flag only while this avatar owns the call; never claims an idle one */
|
|
24136
|
+
updateFlag(preventDisconnections) {
|
|
24137
|
+
var _a, _b;
|
|
24138
|
+
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) {
|
|
24139
|
+
this._global.setData({
|
|
24140
|
+
id: this.id,
|
|
24141
|
+
preventDisconnections
|
|
24142
|
+
});
|
|
24143
|
+
}
|
|
24144
|
+
}
|
|
24145
|
+
/** Clears the record only if it is still ours, so a taken-over avatar can't wipe the new owner */
|
|
24146
|
+
release() {
|
|
24147
|
+
var _a, _b;
|
|
24148
|
+
this._owns = false;
|
|
24149
|
+
if (((_a = this._global.getData()) === null || _a === void 0 ? void 0 : _a.id) === this.id) {
|
|
24150
|
+
(_b = this._logger) === null || _b === void 0 ? void 0 : _b.log('releasing the active avatar session', {
|
|
24151
|
+
data: {
|
|
24152
|
+
id: this.id
|
|
24153
|
+
}
|
|
24154
|
+
});
|
|
24155
|
+
this._global.setData(null);
|
|
24156
|
+
}
|
|
24157
|
+
}
|
|
24158
|
+
dispose() {
|
|
24159
|
+
this.release();
|
|
24160
|
+
this._unsubscribe();
|
|
24161
|
+
this._win.removeEventListener('pagehide', this._onPageHide);
|
|
24162
|
+
}
|
|
24163
|
+
_handle(value) {
|
|
24164
|
+
var _a;
|
|
24165
|
+
const other = value && value.id !== this.id ? value : null;
|
|
24166
|
+
this._conflict.setData(() => other);
|
|
24167
|
+
if (other && this._owns) {
|
|
24168
|
+
this._owns = false;
|
|
24169
|
+
(_a = this._logger) === null || _a === void 0 ? void 0 : _a.log('active avatar session taken over by another avatar', {
|
|
24170
|
+
data: {
|
|
24171
|
+
id: this.id,
|
|
24172
|
+
by: other.id
|
|
24173
|
+
}
|
|
24174
|
+
});
|
|
24175
|
+
this._options.onTakenOver();
|
|
24176
|
+
}
|
|
24177
|
+
}
|
|
24178
|
+
}
|
|
24179
|
+
|
|
23825
24180
|
function setIfChanged(store, next, eq = (a, b) => a === b) {
|
|
23826
24181
|
if (!eq(store.getData(), next)) {
|
|
23827
24182
|
store.setData(() => next);
|
|
@@ -24102,7 +24457,20 @@ class AvatarClientManager extends ActorManager {
|
|
|
24102
24457
|
this._contextSelectorVisible = new TinyDataStore({
|
|
24103
24458
|
initialValue: true
|
|
24104
24459
|
});
|
|
24460
|
+
this._multipleAvatarBehavior = new TinyDataStore({
|
|
24461
|
+
initialValue: {
|
|
24462
|
+
preventDisconnections: false
|
|
24463
|
+
},
|
|
24464
|
+
compare: 'deep'
|
|
24465
|
+
});
|
|
24466
|
+
this._isTakeoverPending = new TinyDataStore({
|
|
24467
|
+
initialValue: false
|
|
24468
|
+
});
|
|
24105
24469
|
this._logger = (_a = this._config.logger) !== null && _a !== void 0 ? _a : avatarClientLoggerFactory.getOrCreateLogger('service', 'avatar-client');
|
|
24470
|
+
this._activeSession = new ActiveSessionCoordinator({
|
|
24471
|
+
onTakenOver: () => this.disconnect(),
|
|
24472
|
+
logger: this._logger
|
|
24473
|
+
});
|
|
24106
24474
|
if (this._config.runtimeSettings.features) {
|
|
24107
24475
|
this._features.setData(() => this._config.runtimeSettings.features);
|
|
24108
24476
|
}
|
|
@@ -24115,6 +24483,19 @@ class AvatarClientManager extends ActorManager {
|
|
|
24115
24483
|
return this._config.runtimeSettings.serverConfig === 'manual' ? (_a = this._config.runtimeSettings.loadingVideoURL) !== null && _a !== void 0 ? _a : null : null;
|
|
24116
24484
|
});
|
|
24117
24485
|
this._setupKalturaContext();
|
|
24486
|
+
this._multipleAvatarBehavior.setData(() => {
|
|
24487
|
+
var _a, _b;
|
|
24488
|
+
return {
|
|
24489
|
+
preventDisconnections: (_b = (_a = this._config.runtimeSettings.multipleAvatarBehavior) === null || _a === void 0 ? void 0 : _a.preventDisconnections) !== null && _b !== void 0 ? _b : false
|
|
24490
|
+
};
|
|
24491
|
+
});
|
|
24492
|
+
this._status.onChanges(status => {
|
|
24493
|
+
if (status === AvatarConnectionStatus.NotConnected || status === AvatarConnectionStatus.Disconnected || status === AvatarConnectionStatus.Error) {
|
|
24494
|
+
this._activeSession.release();
|
|
24495
|
+
}
|
|
24496
|
+
}, {
|
|
24497
|
+
replayLastValue: false
|
|
24498
|
+
});
|
|
24118
24499
|
}
|
|
24119
24500
|
// ────────────────────────────────────────────────────────────────────────
|
|
24120
24501
|
// Private: connection engine
|
|
@@ -24321,6 +24702,18 @@ class AvatarClientManager extends ActorManager {
|
|
|
24321
24702
|
this._logger.log(`Avatar client already connected`);
|
|
24322
24703
|
return;
|
|
24323
24704
|
}
|
|
24705
|
+
const conflict = this._activeSession.conflict.getData();
|
|
24706
|
+
if (conflict) {
|
|
24707
|
+
this._logger.log('Connect pending: another avatar is in a call', {
|
|
24708
|
+
data: {
|
|
24709
|
+
preventDisconnections: conflict.preventDisconnections
|
|
24710
|
+
}
|
|
24711
|
+
});
|
|
24712
|
+
this._pendingTakeoverOptions = options;
|
|
24713
|
+
this._isTakeoverPending.setData(() => true);
|
|
24714
|
+
return;
|
|
24715
|
+
}
|
|
24716
|
+
this._activeSession.claim(this._multipleAvatarBehavior.getData().preventDisconnections);
|
|
24324
24717
|
const connectionId = 'main';
|
|
24325
24718
|
this._logger.log(`Connecting to avatar client with connection ID: ${connectionId}`);
|
|
24326
24719
|
timingTracker.runtimeConnectTime = performance.now();
|
|
@@ -24376,6 +24769,22 @@ class AvatarClientManager extends ActorManager {
|
|
|
24376
24769
|
reconnect() {
|
|
24377
24770
|
this.connect(this._lastConnectOptions);
|
|
24378
24771
|
}
|
|
24772
|
+
confirmTakeover() {
|
|
24773
|
+
var _a;
|
|
24774
|
+
if (!this._isTakeoverPending.getData() || ((_a = this._activeSession.conflict.getData()) === null || _a === void 0 ? void 0 : _a.preventDisconnections)) {
|
|
24775
|
+
return;
|
|
24776
|
+
}
|
|
24777
|
+
const options = this._pendingTakeoverOptions;
|
|
24778
|
+
this._pendingTakeoverOptions = undefined;
|
|
24779
|
+
this._isTakeoverPending.setData(() => false);
|
|
24780
|
+
// Overwriting the page-wide record makes the other avatar end its own call.
|
|
24781
|
+
this._activeSession.claim(this._multipleAvatarBehavior.getData().preventDisconnections);
|
|
24782
|
+
this.connect(options);
|
|
24783
|
+
}
|
|
24784
|
+
cancelTakeover() {
|
|
24785
|
+
this._pendingTakeoverOptions = undefined;
|
|
24786
|
+
this._isTakeoverPending.setData(() => false);
|
|
24787
|
+
}
|
|
24379
24788
|
// ────────────────────────────────────────────────────────────────────────
|
|
24380
24789
|
// Public API: pause / resume
|
|
24381
24790
|
// ────────────────────────────────────────────────────────────────────────
|
|
@@ -24578,6 +24987,23 @@ class AvatarClientManager extends ActorManager {
|
|
|
24578
24987
|
setIsPreviewMode(value) {
|
|
24579
24988
|
this._isPreviewMode.setData(() => value);
|
|
24580
24989
|
}
|
|
24990
|
+
get multipleAvatarBehavior() {
|
|
24991
|
+
return this._multipleAvatarBehavior.consumer;
|
|
24992
|
+
}
|
|
24993
|
+
setMultipleAvatarBehavior(value) {
|
|
24994
|
+
var _a;
|
|
24995
|
+
const preventDisconnections = (_a = value === null || value === void 0 ? void 0 : value.preventDisconnections) !== null && _a !== void 0 ? _a : false;
|
|
24996
|
+
this._multipleAvatarBehavior.setData(() => ({
|
|
24997
|
+
preventDisconnections
|
|
24998
|
+
}));
|
|
24999
|
+
this._activeSession.updateFlag(preventDisconnections);
|
|
25000
|
+
}
|
|
25001
|
+
get activeSessionConflict() {
|
|
25002
|
+
return this._activeSession.conflict;
|
|
25003
|
+
}
|
|
25004
|
+
get isTakeoverPending() {
|
|
25005
|
+
return this._isTakeoverPending.consumer;
|
|
25006
|
+
}
|
|
24581
25007
|
get contextSelectorVisible() {
|
|
24582
25008
|
return this._contextSelectorVisible.consumer;
|
|
24583
25009
|
}
|
|
@@ -24655,8 +25081,9 @@ class AvatarClientManager extends ActorManager {
|
|
|
24655
25081
|
var _a;
|
|
24656
25082
|
(_a = this._unsubscribeContextActive) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
24657
25083
|
this._unsubscribeContextActive = null;
|
|
25084
|
+
this._activeSession.dispose();
|
|
24658
25085
|
this._cleanupConnection();
|
|
24659
25086
|
}
|
|
24660
25087
|
}
|
|
24661
25088
|
|
|
24662
|
-
export { ApplicationFeatures, AvatarClientManager, AvatarConnectionMachineEventTypes, AvatarConnectionMachineId, AvatarNotificationCode, ButtonNames, PageNames, agentRawTextClientToolDeltaSchema, agentRawTextDeltaSchema, agentRawTextSchema, agentTurnToTalkSchema, askPermissionsSchema, avatarClientLoggerFactory, avatarConnectionMachine, avatarRTCClientManager, clientConfigurationSchema, conversationEndedSchema, conversationTimeWarningSchema, createTinyPubsubEventsHandler, createTinyPubsubEventsSource, generatingSpeechSchema, getAvatarDisconnectNotificationVariant, getQueryParam, initializeLogsCollectionIfNeeded, isMappedAvatarDisconnectReason, isMobileDevice, joinCompleteSchema, noAgentSchema, onServerConnectedSchema, pauseExpiryWarningSchema, resetWebsocketClientTeardownState, showAgentSchema, showStartSpeechSchema, stvFinishedTalkingSchema, stvNewSessionSchema, stvSpeechChunkSchema, stvStartedTalkingSchema, timingTracker, unsupportedClientSchema, websocketClientManager, websocketDebugInfo, websocketMessage, websocketNonRecoveredReconnect, websocketRecoveryState, websocketSystemMessage };
|
|
25089
|
+
export { ApplicationFeatures, AvatarClientManager, AvatarConnectionMachineEventTypes, AvatarConnectionMachineId, AvatarNotificationCode, ButtonNames, PageNames, agentRawTextClientToolDeltaSchema, agentRawTextDeltaSchema, agentRawTextSchema, agentTurnToTalkSchema, askPermissionsSchema, avatarClientLoggerFactory, avatarConnectionMachine, avatarRTCClientManager, clientConfigurationSchema, conversationEndedSchema, conversationTimeWarningSchema, createTinyPubsubEventsHandler, createTinyPubsubEventsSource, generatingSpeechSchema, getAvatarDisconnectNotificationVariant, getQueryParam, initializeLogsCollectionIfNeeded, isMappedAvatarDisconnectReason, isMobileDevice, joinCompleteSchema, listenToGlobalTinyDataStore, noAgentSchema, onServerConnectedSchema, pauseExpiryWarningSchema, resetWebsocketClientTeardownState, showAgentSchema, showStartSpeechSchema, stvFinishedTalkingSchema, stvNewSessionSchema, stvSpeechChunkSchema, stvStartedTalkingSchema, timingTracker, unsupportedClientSchema, websocketClientManager, websocketDebugInfo, websocketMessage, websocketNonRecoveredReconnect, websocketRecoveryState, websocketSystemMessage };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kaltura-sdk/avatars",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.21.0",
|
|
4
4
|
"author": "kaltura",
|
|
5
5
|
"license": "AGPL-3.0",
|
|
6
6
|
"repository": {
|
|
@@ -13,13 +13,14 @@
|
|
|
13
13
|
"@kaltura-sdk/rtc-core": "^1.26.2",
|
|
14
14
|
"@local/genie-shared-utils": "^1.0.0",
|
|
15
15
|
"@unisphere/core": "^1.99.0",
|
|
16
|
-
"@unisphere/genie-types": "^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",
|
package/src/index.d.ts
CHANGED
|
@@ -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 @@
|
|
|
1
|
+
export * from './listen-to-global-tiny-data-store';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { UnisphereLoggerType } from '@unisphere/core';
|
|
2
|
+
/**
|
|
3
|
+
* A `TinyDataStore` shared by id between every workspace in this window and,
|
|
4
|
+
* via `postMessage`, every other frame on the page (parent, siblings, nested).
|
|
5
|
+
* Last write wins (timestamp, then window id as tie-break).
|
|
6
|
+
*/
|
|
7
|
+
export declare function listenToGlobalTinyDataStore<T>({ id, initialValue, onChanges, logger, }: {
|
|
8
|
+
id: string;
|
|
9
|
+
initialValue: T;
|
|
10
|
+
onChanges: (value: T) => void;
|
|
11
|
+
logger?: UnisphereLoggerType;
|
|
12
|
+
}): {
|
|
13
|
+
unsubscribe: () => void;
|
|
14
|
+
getData: () => T;
|
|
15
|
+
setData: (value: T) => void;
|
|
16
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ActiveSessionCoordinator } from '../avatar-client-manager/utils/active-session-coordinator';
|
|
2
|
+
import type { listenToGlobalTinyDataStore } from '../global-tiny-data-store';
|
|
3
|
+
type CoordinatorOptions = ConstructorParameters<typeof ActiveSessionCoordinator>[0];
|
|
4
|
+
export declare function getRealTopWindow(): Window;
|
|
5
|
+
export declare function addFrame(parent: Window): Window;
|
|
6
|
+
export declare function installBrowserLikePostMessage(windows: Window[]): void;
|
|
7
|
+
export declare function createFrameApi(win: Window): Promise<{
|
|
8
|
+
listen: (options: Parameters<typeof listenToGlobalTinyDataStore>[0]) => {
|
|
9
|
+
unsubscribe: () => void;
|
|
10
|
+
getData: () => unknown;
|
|
11
|
+
setData: (value: unknown) => void;
|
|
12
|
+
};
|
|
13
|
+
createCoordinator: (options: CoordinatorOptions) => ActiveSessionCoordinator;
|
|
14
|
+
cleanup: () => void;
|
|
15
|
+
}>;
|
|
16
|
+
export declare const flush: () => Promise<unknown>;
|
|
17
|
+
export {};
|