{"version":3,"sources":["src/common.speech/Transcription/ConversationServiceAdapter.ts"],"names":[],"mappings":"AAGA,OAAO,EAEH,iBAAiB,EAKjB,YAAY,EACZ,WAAW,EAEX,OAAO,EAGV,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACH,qBAAqB,EACrB,kBAAkB,EAQrB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACO,eAAe,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,qBAAqB,EACzF,MAAM,YAAY,CAAC;AAYpB,OAAO,EAAE,gCAAgC,EAAE,MAAM,oCAAoC,CAAC;AAYtF;;GAEG;AACH,qBAAa,0BAA2B,SAAQ,qBAAqB;IACjE,OAAO,CAAC,gCAAgC,CAAmC;IAC3E,OAAO,CAAC,iCAAiC,CAAqB;IAC9D,OAAO,CAAC,gCAAgC,CAAS;IACjD,OAAO,CAAC,8BAA8B,CAAkB;IACxD,OAAO,CAAC,8BAA8B,CAA6B;IACnE,OAAO,CAAC,2BAA2B,CAAuB;IAC1D,OAAO,CAAC,iCAAiC,CAAuB;IAChE,OAAO,CAAC,kBAAkB,CAAuB;IACjD,OAAO,CAAC,oBAAoB,CAAU;IACtC,OAAO,CAAC,0BAA0B,CAAc;IAChD,OAAO,CAAC,0BAA0B,CAAU;gBAGxC,cAAc,EAAE,eAAe,EAC/B,iBAAiB,EAAE,kBAAkB,EACrC,WAAW,EAAE,YAAY,EACzB,gBAAgB,EAAE,gBAAgB,EAClC,4BAA4B,EAAE,gCAAgC;IAiB3D,UAAU,IAAI,OAAO;IAIrB,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI;IAS9B,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAQlC,gBAAgB,wCA2BtB;IAED,SAAS,CAAC,cAAc,IAAI,IAAI;IAuBhC,SAAS,CAAC,2BAA2B,CACjC,iBAAiB,EAAE,iBAAiB,EACpC,eAAe,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,IAAI,EAClC,aAAa,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO;IAKjD,SAAS,CAAC,iBAAiB,CACvB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,kBAAkB,EAAE,kBAAkB,EACtC,SAAS,EAAE,qBAAqB,EAChC,KAAK,EAAE,MAAM,GAAG,IAAI;IAoBxB,SAAS,CAAC,IAAI,YAEb;IAED;;;OAGG;IACH,SAAS,CAAC,uBAAuB,CAAC,cAAc,GAAE,OAAe,GAAG,OAAO,CAAC,WAAW,CAAC;IAiExF;;OAEG;IACH,OAAO,CAAC,kCAAkC,CAuUzC;IAED,OAAO,CAAC,gBAAgB;IAmBxB,OAAO,CAAC,gBAAgB;IAwBxB,OAAO,CAAC,eAAe;IAavB,OAAO,CAAC,2BAA2B,CAElC;CAEJ","file":"ConversationServiceAdapter.d.ts","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved.\n// Licensed under the MIT license.\n\nimport {\n    ConnectionEvent,\n    ConnectionMessage,\n    ConnectionOpenResponse,\n    ConnectionState,\n    createNoDashGuid,\n    Deferred,\n    IAudioSource,\n    IConnection,\n    MessageType,\n    Promise,\n    PromiseHelper,\n    PromiseResult\n} from \"../../common/Exports\";\nimport { Sink } from \"../../common/Promise\";\nimport {\n    CancellationErrorCode,\n    CancellationReason,\n    ConversationExpirationEventArgs,\n    ConversationTranslationCanceledEventArgs,\n    ConversationTranslationResult,\n    PropertyId,\n    SessionEventArgs,\n    SpeechRecognitionResult,\n    Translations\n} from \"../../sdk/Exports\";\nimport {\n    AuthInfo, IAuthentication, IConnectionFactory, RecognizerConfig, ServiceRecognizerBase\n} from \"../Exports\";\nimport { ConversationConnectionMessage } from \"./ConversationConnectionMessage\";\nimport { ConversationRequestSession } from \"./ConversationRequestSession\";\nimport {\n    ConversationReceivedTranslationEventArgs,\n    LockRoomEventArgs,\n    MuteAllEventArgs,\n    ParticipantAttributeEventArgs,\n    ParticipantEventArgs,\n    ParticipantsListEventArgs\n} from \"./ConversationTranslatorEventArgs\";\nimport { ConversationTranslatorCommandTypes, ConversationTranslatorMessageTypes, IInternalParticipant } from \"./ConversationTranslatorInterfaces\";\nimport { ConversationTranslatorRecognizer } from \"./ConversationTranslatorRecognizer\";\nimport {\n    CommandResponsePayload,\n    IParticipantPayloadResponse,\n    IParticipantsListPayloadResponse,\n    ITranslationResponsePayload,\n    ParticipantPayloadResponse,\n    ParticipantsListPayloadResponse,\n    SpeechResponsePayload,\n    TextResponsePayload\n} from \"./ServiceMessages/Exports\";\n\n/***\n * The service adapter handles sending and receiving messages to the Conversation Translator websocket.\n */\nexport class ConversationServiceAdapter extends ServiceRecognizerBase {\n    private privConversationServiceConnector: ConversationTranslatorRecognizer;\n    private privConversationConnectionFactory: IConnectionFactory;\n    private privConversationAuthFetchEventId: string;\n    private privConversationAuthentication: IAuthentication;\n    private privConversationRequestSession: ConversationRequestSession;\n    private privConnectionConfigPromise: Promise<IConnection>;\n    private privConversationConnectionPromise: Promise<IConnection>;\n    private privConnectionLoop: Promise<IConnection>;\n    private terminateMessageLoop: boolean;\n    private privLastPartialUtteranceId: string = \"\";\n    private privConversationIsDisposed: boolean;\n\n    public constructor(\n        authentication: IAuthentication,\n        connectionFactory: IConnectionFactory,\n        audioSource: IAudioSource,\n        recognizerConfig: RecognizerConfig,\n        conversationServiceConnector: ConversationTranslatorRecognizer) {\n\n        super(authentication, connectionFactory, audioSource, recognizerConfig, conversationServiceConnector);\n\n        this.privConversationServiceConnector = conversationServiceConnector;\n        this.privConversationAuthentication = authentication;\n        this.receiveMessageOverride = this.receiveConversationMessageOverride;\n        this.recognizeOverride = this.noOp;\n        this.connectImplOverride = this.conversationConnectImpl;\n        this.configConnectionOverride = this.configConnection;\n        this.fetchConnectionOverride = this.fetchConversationConnection;\n        this.disconnectOverride = this.privDisconnect;\n        this.privConversationRequestSession = new ConversationRequestSession(createNoDashGuid());\n        this.privConversationConnectionFactory = connectionFactory;\n        this.privConversationIsDisposed = false;\n    }\n\n    public isDisposed(): boolean {\n        return this.privConversationIsDisposed;\n    }\n\n    public dispose(reason?: string): void {\n        this.privConversationIsDisposed = true;\n        if (this.privConnectionConfigPromise) {\n            this.privConnectionConfigPromise.onSuccessContinueWith((connection: IConnection) => {\n                connection.dispose(reason);\n            });\n        }\n    }\n\n    public sendMessage(message: string): void {\n        this.fetchConversationConnection().onSuccessContinueWith((connection: IConnection) => {\n            connection.send(new ConversationConnectionMessage(\n                MessageType.Text,\n                message));\n        });\n    }\n\n    public sendMessageAsync = (message: string): Promise<boolean> => {\n        const sink: Sink<boolean> = new Sink<boolean>();\n\n        this.fetchConversationConnection().continueWith((antecedent: PromiseResult<IConnection>): void => {\n            try {\n                if (antecedent.isError) {\n                    sink.reject(antecedent.error);\n                } else {\n                    antecedent.result.send(new ConversationConnectionMessage(MessageType.Text, message))\n                        .continueWith((innerAntecedent: PromiseResult<boolean>): void => {\n                            try {\n                                if (innerAntecedent.isError) {\n                                    sink.reject(innerAntecedent.error);\n                                } else {\n                                    sink.resolve(innerAntecedent.result);\n                                }\n                            } catch (e) {\n                                sink.reject(`Unhandled inner error: ${e}`);\n                            }\n                        });\n                }\n            } catch (e) {\n                sink.reject(`Unhandled error: ${e}`);\n            }\n        });\n\n        return new Promise<boolean>(sink);\n    }\n\n    protected privDisconnect(): void {\n        if (this.terminateMessageLoop) {\n            return;\n        }\n        this.cancelRecognition(this.privConversationRequestSession.sessionId,\n            this.privConversationRequestSession.requestId,\n            CancellationReason.Error,\n            CancellationErrorCode.NoError,\n            \"Disconnecting\");\n\n        this.terminateMessageLoop = true;\n        if (this.privConversationConnectionPromise.result().isCompleted) {\n            if (!this.privConversationConnectionPromise.result().isError) {\n                this.privConversationConnectionPromise.result().result.dispose();\n                this.privConversationConnectionPromise = null;\n            }\n        } else {\n            this.privConversationConnectionPromise.onSuccessContinueWith((connection: IConnection) => {\n                connection.dispose();\n            });\n        }\n    }\n\n    protected processTypeSpecificMessages(\n        connectionMessage: ConnectionMessage,\n        successCallback?: (e: any) => void,\n        errorCallBack?: (e: string) => void): boolean {\n        return true;\n    }\n\n    // Cancels recognition.\n    protected cancelRecognition(\n        sessionId: string,\n        requestId: string,\n        cancellationReason: CancellationReason,\n        errorCode: CancellationErrorCode,\n        error: string): void {\n\n        this.terminateMessageLoop = true;\n\n        const cancelEvent: ConversationTranslationCanceledEventArgs = new ConversationTranslationCanceledEventArgs(\n            cancellationReason,\n            error,\n            errorCode,\n            undefined,\n            sessionId);\n\n        try {\n            if (!!this.privConversationServiceConnector.canceled) {\n                this.privConversationServiceConnector.canceled(this.privConversationServiceConnector, cancelEvent);\n            }\n        } catch {\n            // continue on error\n        }\n    }\n\n    protected noOp = (): any => {\n        // operation not supported\n    }\n\n    /**\n     * Establishes a websocket connection to the end point.\n     * @param isUnAuthorized\n     */\n    protected conversationConnectImpl(isUnAuthorized: boolean = false): Promise<IConnection> {\n\n        if (this.privConversationConnectionPromise) {\n            if (this.privConversationConnectionPromise.result().isCompleted &&\n                (this.privConversationConnectionPromise.result().isError\n                    || this.privConversationConnectionPromise.result().result.state() === ConnectionState.Disconnected)) {\n                this.privConnectionId = null;\n                this.privConversationConnectionPromise = null;\n                this.terminateMessageLoop = true;\n                return this.conversationConnectImpl();\n            } else {\n                return this.privConversationConnectionPromise;\n            }\n        }\n\n        this.privConversationAuthFetchEventId = createNoDashGuid();\n\n        // keep the connectionId for reconnect events\n        if (this.privConnectionId === undefined) {\n            this.privConnectionId = createNoDashGuid();\n        }\n\n        this.privConversationRequestSession.onPreConnectionStart(this.privConversationAuthFetchEventId, this.privConnectionId);\n\n        const authPromise = isUnAuthorized ? this.privConversationAuthentication.fetchOnExpiry(this.privConversationAuthFetchEventId) : this.privConversationAuthentication.fetch(this.privConversationAuthFetchEventId);\n\n        this.privConversationConnectionPromise = authPromise\n            .continueWithPromise((result: PromiseResult<AuthInfo>) => {\n                if (result.isError) {\n                    this.privConversationRequestSession.onAuthCompleted(true, result.error);\n                    throw new Error(result.error);\n                } else {\n                    this.privConversationRequestSession.onAuthCompleted(false);\n                }\n\n                const connection: IConnection = this.privConversationConnectionFactory.create(this.privRecognizerConfig, result.result, this.privConnectionId);\n\n                // Attach to the underlying event. No need to hold onto the detach pointers as in the event the connection goes away,\n                // it'll stop sending events.\n                connection.events.attach((event: ConnectionEvent) => {\n                    this.connectionEvents.onEvent(event);\n                });\n\n                return connection.open().onSuccessContinueWithPromise((response: ConnectionOpenResponse): Promise<IConnection> => {\n                    if (response.statusCode === 200) {\n                        this.privConversationRequestSession.onPreConnectionStart(this.privConversationAuthFetchEventId, this.privConnectionId);\n                        this.privConversationRequestSession.onConnectionEstablishCompleted(response.statusCode);\n                        const sessionStartEventArgs: SessionEventArgs = new SessionEventArgs(this.privConversationRequestSession.sessionId);\n                        if (!!this.privConversationServiceConnector.connectionOpened) {\n                            this.privConversationServiceConnector.connectionOpened(this.privConversationServiceConnector, sessionStartEventArgs);\n                        }\n                        return PromiseHelper.fromResult<IConnection>(connection);\n                    } else if (response.statusCode === 403 && !isUnAuthorized) {\n                        return this.conversationConnectImpl(true);\n                    } else {\n                        this.privConversationRequestSession.onConnectionEstablishCompleted(response.statusCode, response.reason);\n                        return PromiseHelper.fromError<IConnection>(`Unable to contact server. StatusCode: ${response.statusCode}, ${this.privRecognizerConfig.parameters.getProperty(PropertyId.SpeechServiceConnection_Endpoint)} Reason: ${response.reason}`);\n                    }\n                });\n            });\n\n        this.privConnectionLoop = this.startMessageLoop();\n        return this.privConversationConnectionPromise;\n    }\n\n    /**\n     * Process incoming websocket messages\n     */\n    private receiveConversationMessageOverride = (\n        successCallback?: (e: any) => void,\n        errorCallBack?: (e: string) => void\n    ): Promise<IConnection> => {\n\n        // we won't rely on the cascading promises of the connection since we want to continually be available to receive messages\n        const communicationCustodian: Deferred<IConnection> = new Deferred<IConnection>();\n\n        this.fetchConversationConnection().on((connection: IConnection): Promise<IConnection> => {\n            return connection.read().onSuccessContinueWithPromise((message: ConversationConnectionMessage): Promise<IConnection> => {\n                const isDisposed: boolean = this.isDisposed();\n                const terminateMessageLoop = (!this.isDisposed() && this.terminateMessageLoop);\n                const sessionId: string = this.privConversationRequestSession.sessionId;\n                let sendFinal: boolean = false;\n                if (isDisposed || terminateMessageLoop) {\n                    // We're done.\n                    communicationCustodian.resolve(undefined);\n                    return PromiseHelper.fromResult<IConnection>(undefined);\n                }\n\n                if (!message) {\n                    return this.receiveConversationMessageOverride();\n                }\n\n                try {\n                    switch (message.conversationMessageType.toLowerCase()) {\n                        case \"info\":\n                        case \"participant_command\":\n                        case \"command\":\n                            const commandPayload: CommandResponsePayload = CommandResponsePayload.fromJSON(message.textBody);\n                            switch (commandPayload.command.toLowerCase()) {\n\n                                /**\n                                 * 'ParticpantList' is the first message sent to the user after the websocket connection has opened.\n                                 * The consuming client must wait for this message to arrive\n                                 * before starting to send their own data.\n                                 */\n                                case \"participantlist\":\n\n                                    const participantsPayload: IParticipantsListPayloadResponse = ParticipantsListPayloadResponse.fromJSON(message.textBody);\n\n                                    const participantsResult: IInternalParticipant[] = participantsPayload.participants.map((p: IParticipantPayloadResponse) => {\n                                        const participant: IInternalParticipant = {\n                                            avatar: p.avatar,\n                                            displayName: p.nickname,\n                                            id: p.participantId,\n                                            isHost: p.ishost,\n                                            isMuted: p.ismuted,\n                                            isUsingTts: p.usetts,\n                                            preferredLanguage: p.locale\n                                        };\n                                        return participant;\n                                    });\n\n                                    if (!!this.privConversationServiceConnector.participantsListReceived) {\n                                        this.privConversationServiceConnector.participantsListReceived(this.privConversationServiceConnector,\n                                            new ParticipantsListEventArgs(participantsPayload.roomid, participantsPayload.token,\n                                                participantsPayload.translateTo, participantsPayload.profanityFilter,\n                                                participantsPayload.roomProfanityFilter, participantsPayload.roomLocked,\n                                                participantsPayload.muteAll, participantsResult, sessionId));\n                                    }\n                                    break;\n\n                                /**\n                                 * 'SetTranslateToLanguages' represents the list of languages being used in the Conversation by all users(?).\n                                 * This is sent at the start of the Conversation\n                                 */\n                                case \"settranslatetolanguages\":\n\n                                    if (!!this.privConversationServiceConnector.participantUpdateCommandReceived) {\n                                        this.privConversationServiceConnector.participantUpdateCommandReceived(this.privConversationServiceConnector,\n                                            new ParticipantAttributeEventArgs(commandPayload.participantId,\n                                                ConversationTranslatorCommandTypes.setTranslateToLanguages,\n                                                commandPayload.value, sessionId));\n                                    }\n\n                                    break;\n\n                                /**\n                                 * 'SetProfanityFiltering' lets the client set the level of profanity filtering.\n                                 * If sent by the participant the setting will effect only their own profanity level.\n                                 * If sent by the host, the setting will effect all participants including the host.\n                                 * Note: the profanity filters differ from Speech Service (?): 'marked', 'raw', 'removed', 'tagged'\n                                 */\n                                case \"setprofanityfiltering\":\n\n                                    if (!!this.privConversationServiceConnector.participantUpdateCommandReceived) {\n                                        this.privConversationServiceConnector.participantUpdateCommandReceived(this.privConversationServiceConnector,\n                                            new ParticipantAttributeEventArgs(commandPayload.participantId,\n                                                ConversationTranslatorCommandTypes.setProfanityFiltering,\n                                                commandPayload.value, sessionId));\n                                    }\n\n                                    break;\n\n                                /**\n                                 * 'SetMute' is sent if the participant has been muted by the host.\n                                 * Check the 'participantId' to determine if the current user has been muted.\n                                 */\n                                case \"setmute\":\n\n                                    if (!!this.privConversationServiceConnector.participantUpdateCommandReceived) {\n                                        this.privConversationServiceConnector.participantUpdateCommandReceived(this.privConversationServiceConnector,\n                                            new ParticipantAttributeEventArgs(commandPayload.participantId,\n                                                ConversationTranslatorCommandTypes.setMute,\n                                                commandPayload.value, sessionId));\n                                    }\n\n                                    break;\n\n                                /**\n                                 * 'SetMuteAll' is sent if the Conversation has been muted by the host.\n                                 */\n                                case \"setmuteall\":\n\n                                    if (!!this.privConversationServiceConnector.muteAllCommandReceived) {\n                                        this.privConversationServiceConnector.muteAllCommandReceived(this.privConversationServiceConnector,\n                                            new MuteAllEventArgs(commandPayload.value as boolean, sessionId));\n                                    }\n\n                                    break;\n\n                                /**\n                                 * 'RoomExpirationWarning' is sent towards the end of the Conversation session to give a timeout warning.\n                                 */\n                                case \"roomexpirationwarning\":\n\n                                    if (!!this.privConversationServiceConnector.conversationExpiration) {\n                                        this.privConversationServiceConnector.conversationExpiration(this.privConversationServiceConnector,\n                                            new ConversationExpirationEventArgs(commandPayload.value as number, this.privConversationRequestSession.sessionId));\n                                    }\n\n                                    break;\n\n                                /**\n                                 * 'SetUseTts' is sent as a confirmation if the user requests TTS to be turned on or off.\n                                 */\n                                case \"setusetts\":\n\n                                    if (!!this.privConversationServiceConnector.participantUpdateCommandReceived) {\n                                        this.privConversationServiceConnector.participantUpdateCommandReceived(this.privConversationServiceConnector,\n                                            new ParticipantAttributeEventArgs(commandPayload.participantId,\n                                                ConversationTranslatorCommandTypes.setUseTTS,\n                                                commandPayload.value, sessionId));\n                                    }\n\n                                    break;\n\n                                /**\n                                 * 'SetLockState' is set if the host has locked or unlocked the Conversation.\n                                 */\n                                case \"setlockstate\":\n\n                                    if (!!this.privConversationServiceConnector.lockRoomCommandReceived) {\n                                        this.privConversationServiceConnector.lockRoomCommandReceived(this.privConversationServiceConnector,\n                                            new LockRoomEventArgs(commandPayload.value as boolean, sessionId));\n                                    }\n\n                                    break;\n\n                                /**\n                                 * 'ChangeNickname' is received if a user changes their display name.\n                                 * Any cached particpiants list should be updated to reflect the display name.\n                                 */\n                                case \"changenickname\":\n\n                                    if (!!this.privConversationServiceConnector.participantUpdateCommandReceived) {\n                                        this.privConversationServiceConnector.participantUpdateCommandReceived(this.privConversationServiceConnector,\n                                            new ParticipantAttributeEventArgs(commandPayload.participantId,\n                                                ConversationTranslatorCommandTypes.changeNickname,\n                                                commandPayload.nickname, sessionId));\n                                    }\n\n                                    break;\n\n                                /**\n                                 * 'JoinSession' is sent when a user joins the Conversation.\n                                 */\n                                case \"joinsession\":\n\n                                    const joinParticipantPayload: ParticipantPayloadResponse = ParticipantPayloadResponse.fromJSON(message.textBody);\n\n                                    const joiningParticipant: IInternalParticipant = {\n                                        avatar: joinParticipantPayload.avatar,\n                                        displayName: joinParticipantPayload.nickname,\n                                        id: joinParticipantPayload.participantId,\n                                        isHost: joinParticipantPayload.ishost,\n                                        isMuted: joinParticipantPayload.ismuted,\n                                        isUsingTts: joinParticipantPayload.usetts,\n                                        preferredLanguage: joinParticipantPayload.locale,\n                                    };\n\n                                    if (!!this.privConversationServiceConnector.participantJoinCommandReceived) {\n                                        this.privConversationServiceConnector.participantJoinCommandReceived(this.privConversationServiceConnector,\n                                            new ParticipantEventArgs(\n                                                joiningParticipant,\n                                                sessionId));\n                                    }\n\n                                    break;\n\n                                /**\n                                 * 'LeaveSession' is sent when a user leaves the Conversation'.\n                                 */\n                                case \"leavesession\":\n\n                                    const leavingParticipant: IInternalParticipant = {\n                                        id: commandPayload.participantId\n                                    };\n\n                                    if (!!this.privConversationServiceConnector.participantLeaveCommandReceived) {\n                                        this.privConversationServiceConnector.participantLeaveCommandReceived(this.privConversationServiceConnector,\n                                            new ParticipantEventArgs(leavingParticipant, sessionId));\n                                    }\n\n                                    break;\n\n                                /**\n                                 * 'DisconnectSession' is sent when a user is disconnected from the session (e.g. network problem).\n                                 * Check the 'ParticipantId' to check whether the message is for the current user.\n                                 */\n                                case \"disconnectsession\":\n\n                                    const disconnectParticipant: IInternalParticipant = {\n                                        id: commandPayload.participantId\n                                    };\n\n                                    break;\n\n                                /**\n                                 * Message not recognized.\n                                 */\n                                default:\n                                    break;\n                            }\n                            break;\n\n                        /**\n                         * 'partial' (or 'hypothesis') represents a unfinalized speech message.\n                         */\n                        case \"partial\":\n\n                        /**\n                         * 'final' (or 'phrase') represents a finalized speech message.\n                         */\n                        case \"final\":\n\n                            const speechPayload: SpeechResponsePayload = SpeechResponsePayload.fromJSON(message.textBody);\n\n                            const speechResult: ConversationTranslationResult = new ConversationTranslationResult(speechPayload.participantId,\n                                this.getTranslations(speechPayload.translations),\n                                speechPayload.language,\n                                undefined,\n                                undefined,\n                                speechPayload.recognition,\n                                undefined,\n                                undefined,\n                                message.textBody,\n                                undefined);\n\n                            if (speechPayload.isFinal) {\n                                // check the length, sometimes empty finals are returned\n                                if (speechResult.text !== undefined && speechResult.text.length > 0) {\n                                    sendFinal = true;\n                                } else if (speechPayload.id === this.privLastPartialUtteranceId) {\n                                    // send final as normal. We had a non-empty partial for this same utterance\n                                    // so sending the empty final is important\n                                    sendFinal = true;\n                                } else {\n                                    // suppress unneeded final\n                                }\n\n                                if (sendFinal) {\n                                    if (!!this.privConversationServiceConnector.translationReceived) {\n                                        this.privConversationServiceConnector.translationReceived(this.privConversationServiceConnector,\n                                            new ConversationReceivedTranslationEventArgs(ConversationTranslatorMessageTypes.final, speechResult, sessionId));\n                                    }\n                                }\n                            } else if (speechResult.text !== undefined) {\n                                this.privLastPartialUtteranceId = speechPayload.id;\n                                if (!!this.privConversationServiceConnector.translationReceived) {\n                                    this.privConversationServiceConnector.translationReceived(this.privConversationServiceConnector,\n                                        new ConversationReceivedTranslationEventArgs(ConversationTranslatorMessageTypes.partial, speechResult, sessionId));\n                                }\n                            }\n\n                            break;\n\n                        /**\n                         * \"translated_message\" is a text message or instant message (IM).\n                         */\n                        case \"translated_message\":\n\n                            const textPayload: TextResponsePayload = TextResponsePayload.fromJSON(message.textBody);\n\n                            const textResult: ConversationTranslationResult = new ConversationTranslationResult(textPayload.participantId,\n                                this.getTranslations(textPayload.translations),\n                                textPayload.language,\n                                undefined,\n                                undefined,\n                                textPayload.originalText,\n                                undefined,\n                                undefined,\n                                undefined,\n                                message.textBody,\n                                undefined);\n\n                            if (!!this.privConversationServiceConnector.translationReceived) {\n                                this.privConversationServiceConnector.translationReceived(this.privConversationServiceConnector,\n                                    new ConversationReceivedTranslationEventArgs(ConversationTranslatorMessageTypes.instantMessage, textResult, sessionId));\n                            }\n                            break;\n\n                        default:\n                            // ignore any unsupported message types\n                            break;\n                    }\n                } catch (e) {\n                    // continue\n                }\n                return this.receiveConversationMessageOverride();\n            });\n        }, (error: string) => {\n            this.terminateMessageLoop = true;\n        });\n\n        return communicationCustodian.promise();\n    }\n\n    private startMessageLoop(): Promise<IConnection> {\n\n        this.terminateMessageLoop = false;\n\n        const messageRetrievalPromise = this.receiveConversationMessageOverride();\n\n        return messageRetrievalPromise.on((r: IConnection) => {\n            return true;\n        }, (error: string) => {\n            this.cancelRecognition(\n                this.privRequestSession ? this.privRequestSession.sessionId : \"\",\n                this.privRequestSession ? this.privRequestSession.requestId : \"\",\n                CancellationReason.Error,\n                CancellationErrorCode.RuntimeError,\n                error);\n        });\n    }\n\n    // Takes an established websocket connection to the endpoint\n    private configConnection(): Promise<IConnection> {\n        if (this.privConnectionConfigPromise) {\n            if (this.privConnectionConfigPromise.result().isCompleted &&\n                (this.privConnectionConfigPromise.result().isError\n                    || this.privConnectionConfigPromise.result().result.state() === ConnectionState.Disconnected)) {\n\n                this.privConnectionConfigPromise = null;\n                return this.configConnection();\n            } else {\n                return this.privConnectionConfigPromise;\n            }\n        }\n\n        if (this.terminateMessageLoop) {\n            return PromiseHelper.fromResult<IConnection>(undefined);\n        }\n\n        this.privConnectionConfigPromise = this.conversationConnectImpl()\n            .onSuccessContinueWith((connection: IConnection): any => {\n                return connection;\n            });\n        return this.privConnectionConfigPromise;\n    }\n\n    private getTranslations(serviceResultTranslations: ITranslationResponsePayload[]): Translations {\n        let translations: Translations;\n\n        if (undefined !== serviceResultTranslations) {\n            translations = new Translations();\n            for (const translation of serviceResultTranslations) {\n                translations.set(translation.lang, translation.translation);\n            }\n        }\n\n        return translations;\n    }\n\n    private fetchConversationConnection = (): Promise<IConnection> => {\n        return this.configConnection();\n    }\n\n}\n"]}