UNPKG

@inworld/web-core

Version:
835 lines (834 loc) 37.3 kB
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 }; } }