@inworld/web-core
Version:
835 lines (834 loc) • 37.3 kB
JavaScript
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
import { ActorType, ControlEventAction, } from '../../proto/ai/inworld/packets/packets.pb.js';
import { GRPC_HOSTNAME } from '../common/constants.js';
import { AudioSessionState, ConnectionState, ConversationState, InworlControlAction, InworldConversationEventType, InworldPacketType, } from '../common/data_structures/index.js';
import { objectsAreEqual } from '../common/helpers.js';
import { InworldHistory } from '../components/history.js';
import { Player } from '../components/sound/player.js';
import { WebSocketConnection, } from '../connection/web-socket.connection.js';
import { Capability } from '../entities/capability.entity.js';
import { SessionContinuation } from '../entities/continuation/session_continuation.entity.js';
import { ErrorReconnectionType, ErrorType, } from '../entities/error.entity.js';
import { ControlEvent } from '../entities/packets/control.entity.js';
import { InworldPacket } from '../entities/packets/inworld_packet.entity.js';
import { Scene } from '../entities/scene.entity.js';
import { SessionToken } from '../entities/session_token.entity.js';
import { EventFactory } from '../factories/event.js';
export class ConnectionService {
constructor(props) {
this.player = Player.getInstance();
this.state = ConnectionState.INACTIVE;
this.audioSessionAction = AudioSessionState.UNKNOWN;
this.characterMapping = {};
this.sceneIsLoaded = false;
this.intervals = [];
this.packetQueue = [];
this.packetQueuePercievedLatency = [];
this.conversations = new Map();
this.cancelResponses = {};
this.MAX_LATENCY_QUEUE_SIZE = 50;
// Store packets in progress to resend them on reconnection.
this.packetsInProgress = {};
this.connectionProps =
props || {};
this.scene = new Scene({
name: this.connectionProps.name,
});
this.config = this.buildConfiguration(this.connectionProps.config);
this.history = new InworldHistory({
audioEnabled: this.config.capabilities.audio,
extension: this.connectionProps.extension,
user: this.connectionProps.user,
scene: this.scene.name,
conversations: this.conversations,
});
this.eventFactory = new EventFactory({
validateData: this.config.validateData,
});
// Bind handlers
this.onReady = this.onReadyHandler.bind(this);
this.onDisconnect = this.onDisconnectHandler.bind(this);
this.onError = this.onErrorHandler.bind(this);
this.onWarning = this.onWarningHandler.bind(this);
this.onMessage = this.onMessageHandler.bind(this);
this.onHistoryChange = this.onHistoryChangeHandler.bind(this);
this.initializeExtension();
this.initializeConnection();
}
isActive() {
return this.state === ConnectionState.ACTIVE;
}
isConnecting() {
return [ConnectionState.ACTIVATING, ConnectionState.RECONNECTING].includes(this.state);
}
isInactive() {
return this.state === ConnectionState.INACTIVE;
}
isAutoReconnected() {
var _a;
return (_a = this.config.connection.autoReconnect) !== null && _a !== void 0 ? _a : true;
}
getSceneName() {
return this.scene.name;
}
getSessionId() {
var _a;
return (_a = this.session) === null || _a === void 0 ? void 0 : _a.sessionId;
}
getCurrentAudioConversation() {
return this.currentAudioConversation;
}
setCurrentAudioConversation(conversation) {
this.currentAudioConversation = conversation;
}
async openManually() {
try {
if (this.isAutoReconnected()) {
throw Error('Impossible to open connection manually with `autoReconnect` enabled');
}
if (!this.isInactive()) {
throw Error('Connection is already open');
}
return this.open();
}
catch (err) {
this.onError(err);
}
}
async close() {
this.cancelScheduler();
this.cancelReconnectScheduler();
this.state = ConnectionState.INACTIVE;
await this.connection.close();
this.clearQueue();
}
getHistory() {
return this.history.get();
}
clearHistory() {
this.history.clear();
}
getEventFactory() {
return this.eventFactory;
}
getTranscript() {
return this.history.getTranscript();
}
getConfig() {
return this.config;
}
getClientConfig() {
return this.connectionProps.config;
}
async getCharacters() {
await this.open();
return this.scene.characters;
}
async getCurrentCharacter() {
await this.open();
return this.getEventFactory().getCurrentCharacter();
}
getCharactersByIds(ids) {
return this.scene.getCharactersByIds(ids);
}
getCharactersByResourceNames(names) {
return this.scene.getCharactersByResourceNames(names);
}
setCurrentCharacter(character) {
this.getEventFactory().setCurrentCharacter(character);
}
removeCharacters(names) {
this.scene = new Scene(Object.assign(Object.assign({}, this.scene), { characters: this.scene.characters.filter((c) => !names.includes(c.resourceName)) }));
}
async open({ force } = {}) {
var _a, _b;
if (!force && !this.isInactive())
return;
try {
await this.loadToken();
if (this.sceneIsLoaded) {
await this.connection.reopenSession(this.session);
}
else {
const { client, sessionContinuation, user } = this.connectionProps;
const sessionProto = await this.connection.openSession({
client,
name: this.scene.name,
sessionContinuation,
user,
session: this.session,
});
this.setSceneFromProtoEvent(sessionProto);
if ((_a = this.scene.history) === null || _a === void 0 ? void 0 : _a.length) {
this.setPreviousState(this.scene.history);
}
}
this.state = ConnectionState.ACTIVE;
await this.reopenConversations();
this.releaseQueue();
await ((_b = this.onReady) === null || _b === void 0 ? void 0 : _b.call(this));
this.scheduleDisconnect();
}
catch (err) {
this.onError(err);
}
}
async change(name, props) {
var _a, _b;
if (!this.sceneIsLoaded) {
throw Error('Unable to change scene that is not loaded yet');
}
this.connectionProps = Object.assign(Object.assign({}, this.connectionProps), { config: Object.assign(Object.assign(Object.assign(Object.assign({}, this.config), ((props === null || props === void 0 ? void 0 : props.capabilities) && {
capabilities: Capability.toProto(props.capabilities),
})), ((props === null || props === void 0 ? void 0 : props.gameSessionId) && {
gameSessionId: props.gameSessionId,
})), ((props === null || props === void 0 ? void 0 : props.user) && {
user: props.user,
})), sessionContinuation: (props === null || props === void 0 ? void 0 : props.sessionContinuation)
? new SessionContinuation(props.sessionContinuation)
: undefined });
this.config = this.buildConfiguration(this.connectionProps.config);
if (!this.isActive()) {
await this.connection.reopenSession(this.session);
}
const sessionProto = await this.connection.updateSession({
name: name !== this.getSceneName() ? name : undefined,
capabilities: (_a = this.connectionProps.config) === null || _a === void 0 ? void 0 : _a.capabilities,
gameSessionId: props === null || props === void 0 ? void 0 : props.gameSessionId,
sessionContinuation: this.connectionProps.sessionContinuation,
});
if (sessionProto) {
this.setSceneFromProtoEvent(sessionProto);
if ((_b = this.scene.history) === null || _b === void 0 ? void 0 : _b.length) {
this.setPreviousState(this.scene.history);
}
}
}
async send(getPacket, props = {}) {
try {
this.cancelScheduler();
if (!this.isActive() && !this.isAutoReconnected()) {
throw Error('Unable to send data due inactive connection');
}
return this.write(getPacket, props);
}
catch (err) {
this.onError(err);
}
}
setAudioSessionAction(action) {
this.audioSessionAction = action;
}
getAudioSessionAction() {
return this.audioSessionAction;
}
async interrupt() {
const packet = this.connectionProps.grpcAudioPlayer.getCurrentPacket();
if (packet) {
await this.interruptByPacket(packet);
}
}
async write(getPacket, props = {}) {
let inworldPacket;
const resolvePacket = () => new Promise((resolve) => {
const interval = setInterval(() => {
if (inworldPacket || this.isInactive()) {
clearInterval(interval);
this.intervals = this.intervals.filter((i) => i !== interval);
resolve(inworldPacket);
}
}, 10);
this.intervals.push(interval);
});
const itemToSend = {
getPacket,
afterWriting: (packet) => {
inworldPacket = packet;
this.afterWriting(inworldPacket);
},
beforeWriting: async (packet) => this.beforeWriting(getPacket, packet),
};
if (this.isActive()) {
this.connection.write(itemToSend);
}
else {
if (props.enforceHighPriority) {
this.packetQueue.unshift(itemToSend);
}
else {
this.packetQueue.push(itemToSend);
}
await this.open();
}
return resolvePacket();
}
afterWriting(packet) {
this.scheduleDisconnect();
this.addPacketToHistory(packet);
}
async beforeWriting(getPacket, packet) {
var _a;
if (packet.isPlayerTypeInText()) {
await this.interruptByPacket(packet);
}
if (packet.isNonSpeechPacket() ||
packet.isPlayerTypeInText() ||
packet.isPushToTalkAudioSessionStart()) {
this.pushToPerceivedLatencyQueue([packet]);
}
else if (packet.isAudioSessionEnd()) {
const found = this.packetQueuePercievedLatency.filter((item) => {
return item.isPushToTalkAudioSessionStart() || item.isAudioSessionEnd();
});
if ((_a = found === null || found === void 0 ? void 0 : found[found.length - 1]) === null || _a === void 0 ? void 0 : _a.isPushToTalkAudioSessionStart()) {
const interactionId = found[found.length - 1].packetId.interactionId;
if (interactionId) {
const updatedAudioSessionEnd = new InworldPacket({
packetId: Object.assign(Object.assign({}, packet.packetId), { interactionId }),
control: new ControlEvent({
action: InworlControlAction.AUDIO_SESSION_END,
}),
routing: packet.routing,
date: packet.date,
type: InworldPacketType.CONTROL,
});
this.pushToPerceivedLatencyQueue([updatedAudioSessionEnd]);
}
}
}
if (packet.isText() || packet.isNarratedAction() || packet.isTrigger()) {
this.packetsInProgress[packet.packetId.interactionId] = getPacket;
}
}
getActualCharacterId(target) {
var _a, _b, _c;
if (target.type !== ActorType.AGENT) {
return target.name;
}
const resourceName = this.characterMapping[target.name];
return ((_c = (_b = (_a = this.scene.getCharactersByResourceNames([resourceName])) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.id) !== null && _c !== void 0 ? _c : target.name);
}
async loadToken() {
if (this.state === ConnectionState.ACTIVATING)
return;
await this.ensureSessionToken({
beforeLoading: () => {
this.state = ConnectionState.ACTIVATING;
},
});
}
async ensureSessionToken(props) {
var _a, _b;
// Generate new session token is it's empty or expired
if (!((_a = this.session) === null || _a === void 0 ? void 0 : _a.expirationTime) || SessionToken.isExpired(this.session)) {
const { sessionId } = this.session || {};
(_b = props === null || props === void 0 ? void 0 : props.beforeLoading) === null || _b === void 0 ? void 0 : _b.call(props);
let sessionToken = await this.connectionProps.generateSessionToken();
// Reuse session id to keep context of previous conversation
if (sessionId) {
sessionToken = Object.assign(Object.assign({}, sessionToken), { sessionId });
}
this.session = sessionToken;
}
return this.session;
}
addInterval(interval) {
this.intervals.push(interval);
}
removeInterval(interval) {
this.intervals = this.intervals.filter((i) => i !== interval);
}
markPacketAsHandled(packet) {
if (!this.config.capabilities.perceivedLatencyReport) {
return;
}
const sentIndex = this.packetQueuePercievedLatency.findIndex((item) => {
const { packetId } = item;
const relyOnSpeech = this.config.capabilities.audio &&
packet.isAudio() &&
(item.isSpeechRecognitionResult() ||
item.isPlayerTypeInText() ||
item.isAudioSessionEnd());
const relyOnNonSpeech = item.isNonSpeechPacket() || !this.config.capabilities.audio;
return ((relyOnSpeech || relyOnNonSpeech) &&
packetId.interactionId &&
packetId.interactionId === packet.packetId.interactionId);
});
if (sentIndex > -1) {
const sent = this.packetQueuePercievedLatency[sentIndex];
this.packetQueuePercievedLatency.splice(sentIndex, 1);
this.send(() => this.getEventFactory().perceivedLatencyWithTypeDetection({
sent,
received: packet,
}));
}
}
scheduleDisconnect() {
if (this.config.connection.disconnectTimeout) {
this.cancelScheduler();
this.disconnectTimeoutId = setTimeout(() => this.close(), this.config.connection.disconnectTimeout);
}
}
setPreviousState(history) {
history.forEach(({ packet, character }) => this.history.addOrUpdate({
grpcAudioPlayer: this.connectionProps.grpcAudioPlayer,
characters: this.eventFactory.getCharacters(),
packet: this.extension.convertPacketFromProto(packet),
fromHistory: true,
fromHistoryCharacter: character,
}));
const diff = this.history.get();
this.onHistoryChange(diff, { diff: { added: diff } });
}
cancelScheduler() {
if (this.disconnectTimeoutId) {
clearTimeout(this.disconnectTimeoutId);
}
}
cancelReconnectScheduler() {
if (this.reconnectTimeoutId) {
clearTimeout(this.reconnectTimeoutId);
}
}
reopenConversations() {
const resolveConversations = () => new Promise((resolve) => {
const interval = setInterval(() => {
let active = true;
this.conversations.forEach((conversation) => {
if (conversation.state !== ConversationState.ACTIVE) {
active = false;
}
});
if (active) {
clearInterval(interval);
this.intervals = this.intervals.filter((i) => i !== interval);
resolve();
}
}, 10);
this.intervals.push(interval);
});
let sent = false;
this.conversations.forEach((conversation) => {
if (conversation.state === ConversationState.INACTIVE) {
conversation.service.updateParticipants(conversation.service.getParticipants());
sent = true;
}
});
return sent ? resolveConversations() : Promise.resolve();
}
releaseQueue() {
this.packetQueue.forEach((item) => this.connection.write(item));
this.packetQueue = [];
this.characterMapping = {};
}
clearQueue() {
this.intervals.forEach((i) => {
clearInterval(i);
});
this.intervals = [];
this.packetQueue = [];
this.packetQueuePercievedLatency = [];
}
async onReadyHandler() {
var _a, _b;
(_b = (_a = this.connectionProps).onReady) === null || _b === void 0 ? void 0 : _b.call(_a);
}
async onDisconnectHandler() {
var _a, _b;
this.state = ConnectionState.INACTIVE;
this.audioSessionAction = AudioSessionState.UNKNOWN;
this.conversations.forEach((conversation) => {
conversation.state = ConversationState.INACTIVE;
});
await ((_b = (_a = this.connectionProps).onDisconnect) === null || _b === void 0 ? void 0 : _b.call(_a));
}
async onErrorHandler(err) {
var _a, _b, _c, _d;
this.state = ConnectionState.INACTIVE;
const status = (_a = err.details) === null || _a === void 0 ? void 0 : _a[0];
const interactionIds = Object.keys(this.packetsInProgress);
let needToReopen = !!interactionIds.length &&
[ErrorReconnectionType.IMMEDIATE, ErrorReconnectionType.TIMEOUT].includes(status === null || status === void 0 ? void 0 : status.reconnectType);
// Change internal state based on error type.
switch (status === null || status === void 0 ? void 0 : status.errorType) {
case ErrorType.AUDIO_SESSION_EXPIRED:
this.setAudioSessionAction(AudioSessionState.UNKNOWN);
break;
case ErrorType.SESSION_TOKEN_EXPIRED:
case ErrorType.SESSION_TOKEN_INVALID:
this.session = Object.assign(Object.assign({}, this.session), { expirationTime: undefined });
needToReopen = true;
status.reconnectType = ErrorReconnectionType.IMMEDIATE;
break;
case ErrorType.SESSION_INVALID:
this.session = undefined;
this.sceneIsLoaded = false;
this.characterMapping = this.scene.characters.reduce((acc, character) => (Object.assign(Object.assign({}, acc), { [character.id]: character.resourceName })), this.characterMapping);
this.scene = new Scene({
name: this.getSceneName(),
});
needToReopen = true;
status.reconnectType = ErrorReconnectionType.IMMEDIATE;
break;
}
// Check if the same error occurs multiple times.
const sameError = !!((_b = this.previousError) === null || _b === void 0 ? void 0 : _b.status) &&
!!status &&
objectsAreEqual(this.previousError.status, status, [
'errorType',
'reconnectType',
'reconnectTime',
'maxRetries',
]);
this.previousError = sameError
? {
attempts: this.previousError.attempts + 1,
status: this.previousError.status,
}
: { attempts: 0, status };
// If the same error occurs multiple times (> maxRetries), we need to stop reconnection attempts.
// Also, we need to stop reconnection attempts if the reconnection is impossible due some reasons.
if ((sameError && this.previousError.attempts >= ((_c = status.maxRetries) !== null && _c !== void 0 ? _c : 0)) ||
!status ||
(status &&
![
ErrorReconnectionType.IMMEDIATE,
ErrorReconnectionType.TIMEOUT,
].includes(status.reconnectType))) {
needToReopen = false;
const id = interactionIds.pop();
if (id) {
delete this.packetsInProgress[id];
}
}
if (!needToReopen) {
const handler = (_d = this.connectionProps.onError) !== null && _d !== void 0 ? _d : (() => {
console.error(err);
});
handler(err);
return;
}
this.history.filter({
history: (item) => !interactionIds.includes(item.interactionId),
queue: (item) => !interactionIds.includes(item.interactionId),
});
this.state = ConnectionState.RECONNECTING;
const delay = status.reconnectTime
? new Date(status.reconnectTime).getTime() - Date.now()
: 0;
this.packetQueue = [...this.getPacketsToSentOnOpen(), ...this.packetQueue];
if (status.reconnectType === ErrorReconnectionType.TIMEOUT && delay > 0) {
this.cancelReconnectScheduler();
this.reconnectTimeoutId = setTimeout(() => {
this.open({ force: true });
}, delay);
}
else {
this.open({ force: true });
}
}
async onWarningHandler(message) {
var _a;
const handler = (_a = this.connectionProps.onWarning) !== null && _a !== void 0 ? _a : ((message) => {
var _a;
if ((_a = message.control) === null || _a === void 0 ? void 0 : _a.description) {
console.warn(message.control.description);
}
});
return handler(message);
}
async onMessageHandler(packet) {
var _a, _b;
const { onMessage, grpcAudioPlayer } = this.connectionProps;
const inworldPacket = this.extension.convertPacketFromProto(packet);
const interactionId = inworldPacket.packetId.interactionId;
const conversationId = inworldPacket.packetId.conversationId;
const conversation = conversationId && this.conversations.get(conversationId);
// Skip packets that are not attached to any conversation.
if (inworldPacket.shouldHaveConversationId() && !conversation) {
// Pass packet to external callback.
onMessage === null || onMessage === void 0 ? void 0 : onMessage(inworldPacket);
return;
}
// Update session state.
if (((_a = packet.control) === null || _a === void 0 ? void 0 : _a.action) === ControlEventAction.CURRENT_SCENE_STATUS &&
packet.control.currentSceneStatus) {
this.setSceneFromProtoEvent({
sceneStatus: packet.control.currentSceneStatus,
});
}
// Update conversation state.
if (((_b = inworldPacket.control) === null || _b === void 0 ? void 0 : _b.conversation) && conversation) {
this.conversations.set(inworldPacket.packetId.conversationId, {
service: conversation.service,
state: [
InworldConversationEventType.STARTED,
InworldConversationEventType.UPDATED,
].includes(inworldPacket.control.conversation.type)
? ConversationState.ACTIVE
: ConversationState.INACTIVE,
});
}
// Don't pass text packet outside for interrupred interaction.
if (inworldPacket.isText() &&
!inworldPacket.routing.source.isPlayer &&
this.cancelResponses[interactionId]) {
this.sendCancelResponses({
interactionId,
utteranceId: [packet.packetId.utteranceId],
}, conversationId);
return;
}
if (inworldPacket.isSpeechRecognitionResult()) {
const { indexStart, indexEnd } = this.findLastAudioSessionIndexes();
if (indexStart >= 0 && indexEnd < indexStart) {
const audioSessionStart = this.packetQueuePercievedLatency[indexStart];
this.packetQueuePercievedLatency[indexStart] = new InworldPacket({
packetId: Object.assign(Object.assign({}, audioSessionStart === null || audioSessionStart === void 0 ? void 0 : audioSessionStart.packetId), { interactionId: inworldPacket.packetId.interactionId }),
control: new ControlEvent(audioSessionStart.control),
routing: audioSessionStart.routing,
date: audioSessionStart === null || audioSessionStart === void 0 ? void 0 : audioSessionStart.date,
type: InworldPacketType.CONTROL,
});
}
else {
this.pushToPerceivedLatencyQueue([inworldPacket]);
}
}
// Send cancel response event in case of player talking.
if (inworldPacket.isText() && inworldPacket.routing.source.isPlayer) {
await this.interruptByPacket(inworldPacket);
// Play audio or silence.
}
else if (inworldPacket.isAudio() || inworldPacket.isSilence()) {
if (!this.cancelResponses[interactionId]) {
this.addPacketToHistory(inworldPacket);
grpcAudioPlayer.addToQueue({
packet: inworldPacket,
onBeforePlaying: (packet) => {
this.markPacketAsHandled(packet);
const diff = this.history.update(packet);
if (diff.length) {
this.onHistoryChange(this.getHistory(), {
diff: { added: diff },
conversationId,
});
}
},
onAfterPlaying: (packet) => {
var _a;
const diff = this.history.update(packet);
if (diff.length) {
(_a = this.onHistoryChange) === null || _a === void 0 ? void 0 : _a.call(this, this.getHistory(), {
diff: { added: diff },
conversationId: packet.packetId.conversationId,
});
}
},
});
}
// Delete info about cancel responses on interaction end.
}
else if (inworldPacket.isInteractionEnd()) {
// Delete packet that was successfully applied on the server side.
delete this.packetsInProgress[interactionId];
// Clear previous error.
this.previousError = undefined;
// Delete cancel responses.
delete this.cancelResponses[interactionId];
}
else if (inworldPacket.isWarning()) {
this.onWarning(inworldPacket);
}
// Add packet to history.
// Audio and silence packets were added to history earlier.
if (!inworldPacket.isAudio() && !inworldPacket.isSilence()) {
this.addPacketToHistory(inworldPacket);
}
// Handle latency ping pong.
if (inworldPacket.isPingPongReport()) {
this.send(() => this.getEventFactory().pong(inworldPacket.packetId, packet.latencyReport.pingPong.pingTimestamp), {
enforceHighPriority: true,
});
// Don't pass text packet outside.
return;
}
// Pass packet to external callback.
onMessage === null || onMessage === void 0 ? void 0 : onMessage(inworldPacket);
}
async onHistoryChangeHandler(history, props) {
var _a, _b;
(_b = (_a = this.connectionProps).onHistoryChange) === null || _b === void 0 ? void 0 : _b.call(_a, history, props);
}
initializeConnection() {
const { webRtcLoopbackBiDiSession, grpcAudioPlayer } = this.connectionProps;
this.connection = new WebSocketConnection({
config: this.config,
onDisconnect: this.onDisconnect,
onReady: async () => {
await webRtcLoopbackBiDiSession.startSession(new MediaStream(), grpcAudioPlayer.getPlaybackStream());
this.player.setStream(webRtcLoopbackBiDiSession.getPlaybackLoopbackStream());
},
onError: this.onError,
onMessage: this.onMessage,
extension: this.extension,
eventFactory: this.eventFactory,
});
}
initializeExtension() {
var _a;
const extension = (_a = this.connectionProps.extension) !== null && _a !== void 0 ? _a : {};
this.extension = Object.assign({ convertPacketFromProto: (proto) => InworldPacket.fromProto(proto) }, extension);
}
async interruptByPacket(packet) {
const { grpcAudioPlayer } = this.connectionProps;
if (!this.config.capabilities.interruptions)
return;
const packets = await grpcAudioPlayer.stopForInteraction(packet.packetId.interactionId);
if (packets.length) {
const { interactionId, conversationId } = packets[0].packetId;
this.sendCancelResponses({
interactionId,
utteranceId: packets.map((packet) => packet.packetId.utteranceId),
}, conversationId);
}
}
sendCancelResponses(cancelResponses, conversationId) {
var _a, _b, _c, _d, _e;
const characters = (_b = (_a = this.conversations.get(conversationId)) === null || _a === void 0 ? void 0 : _a.service.getCharacters()) !== null && _b !== void 0 ? _b : [];
if (cancelResponses.interactionId && characters.length === 1) {
this.send(() => this.getEventFactory().cancelResponse(cancelResponses));
this.cancelResponses = Object.assign(Object.assign({}, this.cancelResponses), { [cancelResponses.interactionId]: true });
const interruptionData = {
utteranceId: (_c = cancelResponses.utteranceId) !== null && _c !== void 0 ? _c : [],
interactionId: cancelResponses.interactionId,
};
(_e = (_d = this.connectionProps).onInterruption) === null || _e === void 0 ? void 0 : _e.call(_d, interruptionData);
this.history.filter({
history: (item) => !interruptionData.utteranceId.includes(item.id),
queue: (item) => item.interactionId !== interruptionData.interactionId &&
!interruptionData.utteranceId.includes(item.id),
});
}
}
addPacketToHistory(packet) {
const diff = this.history.addOrUpdate({
grpcAudioPlayer: this.connectionProps.grpcAudioPlayer,
characters: this.eventFactory.getCharacters(),
packet,
});
if (diff.length) {
this.onHistoryChange(this.getHistory(), {
diff: { added: diff },
conversationId: packet.packetId.conversationId,
});
this.markPacketAsHandled(packet);
}
}
getPacketsToSentOnOpen() {
let packets = [];
if (this.state === ConnectionState.RECONNECTING) {
const notAppliedPackets = Object.assign({}, this.packetsInProgress);
const cancellationPackets = [];
const reconnectionPackets = [];
const history = this.history.get();
const lastItem = history[history.length - 1];
this.packetsInProgress = {};
if (lastItem === null || lastItem === void 0 ? void 0 : lastItem.interactionId) {
cancellationPackets.push({
getPacket: () => this.getEventFactory().cancelResponse({
interactionId: lastItem.interactionId,
}),
});
}
Object.keys(notAppliedPackets).forEach((interactionId) => {
const getPacket = notAppliedPackets[interactionId];
reconnectionPackets.push({
getPacket,
afterWriting: this.afterWriting.bind(this),
beforeWriting: (packet) => this.beforeWriting(getPacket, packet),
convertPacket: (proto) => {
var _a, _b, _c;
if ((_a = proto.routing) === null || _a === void 0 ? void 0 : _a.target) {
proto.routing.target.name = this.getActualCharacterId(proto.routing.target);
}
else if ((_c = (_b = proto.routing) === null || _b === void 0 ? void 0 : _b.targets) === null || _c === void 0 ? void 0 : _c.length) {
proto.routing.targets = proto.routing.targets.map((target) => {
target.name = this.getActualCharacterId(target);
return target;
});
}
return proto;
},
});
});
packets = [...cancellationPackets, ...packets, ...reconnectionPackets];
}
return packets;
}
ensureCurrentCharacter() {
const factory = this.getEventFactory();
const currentCharacter = factory.getCurrentCharacter();
const sameCharacter = currentCharacter
? this.scene.characters.find((c) => c.resourceName === (currentCharacter === null || currentCharacter === void 0 ? void 0 : currentCharacter.resourceName))
: undefined;
factory.setCurrentCharacter(sameCharacter !== null && sameCharacter !== void 0 ? sameCharacter : this.scene.characters[0]);
factory.setCharacters(this.scene.characters);
}
setSceneFromProtoEvent(proto) {
var _a, _b;
this.sceneIsLoaded = true;
this.scene = Scene.fromProto({
sceneStatus: proto.sceneStatus,
sessionHistory: proto.sessionHistory,
});
(_b = (_a = this.connectionProps.extension) === null || _a === void 0 ? void 0 : _a.afterLoadScene) === null || _b === void 0 ? void 0 : _b.call(_a, proto.sceneStatus);
this.ensureCurrentCharacter();
}
buildConfiguration(clientConfig = {}) {
const { connection = {}, capabilities = {} } = clientConfig, restConfig = __rest(clientConfig, ["connection", "capabilities"]);
const { gateway } = connection;
return Object.assign(Object.assign({}, restConfig), { connection: Object.assign(Object.assign({}, connection), { gateway: this.ensureGateway(gateway) }), capabilities: Capability.toProto(capabilities) });
}
ensureGateway(gateway) {
var _a, _b;
return {
hostname: (_a = gateway === null || gateway === void 0 ? void 0 : gateway.hostname) !== null && _a !== void 0 ? _a : GRPC_HOSTNAME,
ssl: (_b = gateway === null || gateway === void 0 ? void 0 : gateway.ssl) !== null && _b !== void 0 ? _b : true,
};
}
pushToPerceivedLatencyQueue(packets) {
if (!this.config.capabilities.perceivedLatencyReport) {
return;
}
this.packetQueuePercievedLatency.push(...packets);
if (this.packetQueuePercievedLatency.length > this.MAX_LATENCY_QUEUE_SIZE) {
this.packetQueuePercievedLatency.shift();
}
}
findLastAudioSessionIndexes() {
let indexStart = -1;
let indexEnd = -1;
for (let i = this.packetQueuePercievedLatency.length - 1; i >= 0 && indexStart < 0; i--) {
if (this.packetQueuePercievedLatency[i].isPushToTalkAudioSessionStart()) {
indexStart = i;
}
}
for (let i = this.packetQueuePercievedLatency.length - 1; i >= 0 && indexEnd < 0; i--) {
if (this.packetQueuePercievedLatency[i].isAudioSessionEnd()) {
indexEnd = i;
}
}
return { indexStart, indexEnd };
}
}