{"version":3,"sources":["src/common.speech/DialogServiceAdapter.ts"],"names":[],"mappings":"AAIA,OAAO,EAQH,YAAY,EACZ,gBAAgB,EAIhB,OAAO,EAGV,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EAGH,qBAAqB,EACrB,kBAAkB,EAClB,sBAAsB,EAStB,uBAAuB,EAC1B,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAOH,qBAAqB,EAIxB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAY,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEvE,OAAO,EAAE,uBAAuB,EAAE,MAAM,oCAAoC,CAAC;AAE7E,qBAAa,oBAAqB,SAAQ,qBAAqB;IAC3D,OAAO,CAAC,0BAA0B,CAAyB;IAC3D,OAAO,CAAC,2BAA2B,CAAqB;IACxD,OAAO,CAAC,0BAA0B,CAAS;IAC3C,OAAO,CAAC,oBAAoB,CAAU;IACtC,OAAO,CAAC,wBAAwB,CAAkB;IAClD,OAAO,CAAC,qBAAqB,CAAe;IAC5C,OAAO,CAAC,wBAAwB,CAAiB;IAIjD,OAAO,CAAC,2BAA2B,CAAuB;IAI1D,OAAO,CAAC,2BAA2B,CAAuB;IAE1D,OAAO,CAAC,mBAAmB,CAAuC;IAClE,OAAO,CAAC,kBAAkB,CAAuB;IACjD,OAAO,CAAC,oBAAoB,CAAU;IACtC,OAAO,CAAC,eAAe,CAAU;IAKjC,OAAO,CAAC,oBAAoB,CAAgC;gBAGxD,cAAc,EAAE,eAAe,EAC/B,iBAAiB,EAAE,kBAAkB,EACrC,WAAW,EAAE,YAAY,EACzB,gBAAgB,EAAE,gBAAgB,EAClC,sBAAsB,EAAE,sBAAsB;IAoB3C,UAAU,IAAI,OAAO;IAIrB,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI;IAS9B,WAAW,4BAsBjB;IAED,SAAS,CAAC,cAAc,IAAI,IAAI;IAsBhC,SAAS,CAAC,2BAA2B,CACjC,iBAAiB,EAAE,uBAAuB,EAC1C,eAAe,CAAC,EAAE,CAAC,CAAC,EAAE,uBAAuB,KAAK,IAAI,EACtD,aAAa,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI;IA4H9C,SAAS,CAAC,iBAAiB,CACvB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,kBAAkB,EAAE,kBAAkB,EACtC,SAAS,EAAE,qBAAqB,EAChC,KAAK,EAAE,MAAM,EACb,kBAAkB,EAAE,CAAC,CAAC,EAAE,uBAAuB,KAAK,IAAI,GAAG,IAAI;IA0CnE,SAAS,CAAC,UAAU,gIA2Df;IAED,SAAS,CAAC,SAAS,0DAoGlB;IAGL,OAAO,CAAC,iBAAiB;IA6DzB,OAAO,CAAC,4BAA4B,CA2G/B;IAEL,OAAO,CAAC,gBAAgB;IAcxB,OAAO,CAAC,gBAAgB;IAyBxB,OAAO,CAAC,qBAAqB,CAE5B;IAED,OAAO,CAAC,oBAAoB;IAM5B,OAAO,CAAC,eAAe,CAetB;IAED,OAAO,CAAC,gBAAgB,CAmBvB;IAED,OAAO,CAAC,kBAAkB;CAkB7B","file":"DialogServiceAdapter.d.ts","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved.\n// Licensed under the MIT license.\n\nimport { ReplayableAudioNode } from \"../common.browser/Exports\";\nimport {\n    ConnectionEvent,\n    ConnectionMessage,\n    ConnectionOpenResponse,\n    ConnectionState,\n    createGuid,\n    createNoDashGuid,\n    Deferred,\n    IAudioSource,\n    IAudioStreamNode,\n    IConnection,\n    IStreamChunk,\n    MessageType,\n    Promise,\n    PromiseHelper,\n    PromiseResult,\n} from \"../common/Exports\";\nimport { PullAudioOutputStreamImpl } from \"../sdk/Audio/AudioOutputStream\";\nimport { AudioStreamFormatImpl } from \"../sdk/Audio/AudioStreamFormat\";\nimport {\n    ActivityReceivedEventArgs,\n    AudioOutputStream,\n    CancellationErrorCode,\n    CancellationReason,\n    DialogServiceConnector,\n    PropertyCollection,\n    PropertyId,\n    PullAudioOutputStream,\n    RecognitionEventArgs,\n    ResultReason,\n    SessionEventArgs,\n    SpeechRecognitionCanceledEventArgs,\n    SpeechRecognitionEventArgs,\n    SpeechRecognitionResult,\n} from \"../sdk/Exports\";\nimport { DialogServiceTurnStateManager } from \"./DialogServiceTurnStateManager\";\nimport {\n    AgentConfig,\n    CancellationErrorCodePropertyName,\n    EnumTranslation,\n    ISpeechConfigAudioDevice,\n    RecognitionStatus,\n    RequestSession,\n    ServiceRecognizerBase,\n    SimpleSpeechPhrase,\n    SpeechDetected,\n    SpeechHypothesis,\n} from \"./Exports\";\nimport { AuthInfo, IAuthentication } from \"./IAuthentication\";\nimport { IConnectionFactory } from \"./IConnectionFactory\";\nimport { RecognitionMode, RecognizerConfig } from \"./RecognizerConfig\";\nimport { ActivityPayloadResponse } from \"./ServiceMessages/ActivityResponsePayload\";\nimport { SpeechConnectionMessage } from \"./SpeechConnectionMessage.Internal\";\n\nexport class DialogServiceAdapter extends ServiceRecognizerBase {\n    private privDialogServiceConnector: DialogServiceConnector;\n    private privDialogConnectionFactory: IConnectionFactory;\n    private privDialogAuthFetchEventId: string;\n    private privDialogIsDisposed: boolean;\n    private privDialogAuthentication: IAuthentication;\n    private privDialogAudioSource: IAudioSource;\n    private privDialogRequestSession: RequestSession;\n\n    // A promise for a configured connection.\n    // Do not consume directly, call fetchDialogConnection instead.\n    private privConnectionConfigPromise: Promise<IConnection>;\n\n    // A promise for a connection, but one that has not had the speech context sent yet.\n    // Do not consume directly, call fetchDialogConnection instead.\n    private privDialogConnectionPromise: Promise<IConnection>;\n\n    private privSuccessCallback: (e: SpeechRecognitionResult) => void;\n    private privConnectionLoop: Promise<IConnection>;\n    private terminateMessageLoop: boolean;\n    private agentConfigSent: boolean;\n\n    // Turns are of two kinds:\n    // 1: SR turns, end when the SR result is returned and then turn end.\n    // 2: Service turns where an activity is sent by the service along with the audio.\n    private privTurnStateManager: DialogServiceTurnStateManager;\n\n    public constructor(\n        authentication: IAuthentication,\n        connectionFactory: IConnectionFactory,\n        audioSource: IAudioSource,\n        recognizerConfig: RecognizerConfig,\n        dialogServiceConnector: DialogServiceConnector) {\n\n        super(authentication, connectionFactory, audioSource, recognizerConfig, dialogServiceConnector);\n\n        this.privDialogServiceConnector = dialogServiceConnector;\n        this.privDialogAuthentication = authentication;\n        this.receiveMessageOverride = this.receiveDialogMessageOverride;\n        this.privTurnStateManager = new DialogServiceTurnStateManager();\n        this.recognizeOverride = this.listenOnce;\n        this.connectImplOverride = this.dialogConnectImpl;\n        this.configConnectionOverride = this.configConnection;\n        this.fetchConnectionOverride = this.fetchDialogConnection;\n        this.disconnectOverride = this.privDisconnect;\n        this.privDialogAudioSource = audioSource;\n        this.privDialogRequestSession = new RequestSession(audioSource.id());\n        this.privDialogConnectionFactory = connectionFactory;\n        this.privDialogIsDisposed = false;\n        this.agentConfigSent = false;\n    }\n\n    public isDisposed(): boolean {\n        return this.privDialogIsDisposed;\n    }\n\n    public dispose(reason?: string): void {\n        this.privDialogIsDisposed = 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        const interactionGuid: string = createGuid();\n        const requestId: string = createNoDashGuid();\n\n        const agentMessage: any = {\n            context: {\n                interactionId: interactionGuid\n            },\n            messagePayload: message,\n            version: 0.5\n        };\n\n        const agentMessageJson = JSON.stringify(agentMessage);\n\n        this.fetchDialogConnection().onSuccessContinueWith((connection: IConnection) => {\n            connection.send(new SpeechConnectionMessage(\n                MessageType.Text,\n                \"agent\",\n                requestId,\n                \"application/json\",\n                agentMessageJson));\n        });\n    }\n\n    protected privDisconnect(): void {\n        this.cancelRecognition(this.privDialogRequestSession.sessionId,\n            this.privDialogRequestSession.requestId,\n            CancellationReason.Error,\n            CancellationErrorCode.NoError,\n            \"Disconnecting\",\n            undefined);\n\n        this.terminateMessageLoop = true;\n        this.agentConfigSent = false;\n        if (this.privDialogConnectionPromise.result().isCompleted) {\n            if (!this.privDialogConnectionPromise.result().isError) {\n                this.privDialogConnectionPromise.result().result.dispose();\n                this.privDialogConnectionPromise = null;\n            }\n        } else {\n            this.privDialogConnectionPromise.onSuccessContinueWith((connection: IConnection) => {\n                connection.dispose();\n            });\n        }\n    }\n\n    protected processTypeSpecificMessages(\n        connectionMessage: SpeechConnectionMessage,\n        successCallback?: (e: SpeechRecognitionResult) => void,\n        errorCallBack?: (e: string) => void): void {\n\n        const resultProps: PropertyCollection = new PropertyCollection();\n        if (connectionMessage.messageType === MessageType.Text) {\n            resultProps.setProperty(PropertyId.SpeechServiceResponse_JsonResult, connectionMessage.textBody);\n        }\n\n        let result: SpeechRecognitionResult;\n\n        switch (connectionMessage.path.toLowerCase()) {\n            case \"speech.phrase\":\n                const speechPhrase: SimpleSpeechPhrase = SimpleSpeechPhrase.fromJSON(connectionMessage.textBody);\n\n                this.privDialogRequestSession.onPhraseRecognized(this.privDialogRequestSession.currentTurnAudioOffset + speechPhrase.Offset + speechPhrase.Duration);\n\n                if (speechPhrase.RecognitionStatus === RecognitionStatus.Success) {\n                    const args: SpeechRecognitionEventArgs = this.fireEventForResult(speechPhrase, resultProps);\n                    if (!!this.privDialogServiceConnector.recognized) {\n                        try {\n                            this.privDialogServiceConnector.recognized(this.privDialogServiceConnector, args);\n                            /* tslint:disable:no-empty */\n                        } catch (error) {\n                            // Not going to let errors in the event handler\n                            // trip things up.\n                        }\n                    }\n\n                    // report result to promise.\n                    if (!!this.privSuccessCallback) {\n                        try {\n                            this.privSuccessCallback(args.result);\n                        } catch (e) {\n                            if (!!errorCallBack) {\n                                errorCallBack(e);\n                            }\n                        }\n                        // Only invoke the call back once.\n                        // and if it's successful don't invoke the\n                        // error after that.\n                        this.privSuccessCallback = undefined;\n                        errorCallBack = undefined;\n                    }\n                }\n                break;\n            case \"speech.hypothesis\":\n                const hypothesis: SpeechHypothesis = SpeechHypothesis.fromJSON(connectionMessage.textBody);\n                const offset: number = hypothesis.Offset + this.privDialogRequestSession.currentTurnAudioOffset;\n\n                result = new SpeechRecognitionResult(\n                    this.privDialogRequestSession.requestId,\n                    ResultReason.RecognizingSpeech,\n                    hypothesis.Text,\n                    hypothesis.Duration,\n                    offset,\n                    undefined,\n                    connectionMessage.textBody,\n                    resultProps);\n\n                this.privDialogRequestSession.onHypothesis(offset);\n\n                const ev = new SpeechRecognitionEventArgs(result, hypothesis.Duration, this.privDialogRequestSession.sessionId);\n\n                if (!!this.privDialogServiceConnector.recognizing) {\n                    try {\n                        this.privDialogServiceConnector.recognizing(this.privDialogServiceConnector, ev);\n                        /* tslint:disable:no-empty */\n                    } catch (error) {\n                        // Not going to let errors in the event handler\n                        // trip things up.\n                    }\n                }\n                break;\n\n            case \"audio\":\n                {\n                    const audioRequestId = connectionMessage.requestId.toUpperCase();\n                    const turn = this.privTurnStateManager.GetTurn(audioRequestId);\n                    try {\n                        // Empty binary message signals end of stream.\n                        if (!connectionMessage.binaryBody) {\n                            turn.endAudioStream();\n                        } else {\n                            turn.audioStream.write(connectionMessage.binaryBody);\n                        }\n                    } catch (error) {\n                        // Not going to let errors in the event handler\n                        // trip things up.\n                    }\n                }\n                break;\n\n            case \"response\":\n                {\n                    const responseRequestId = connectionMessage.requestId.toUpperCase();\n                    const activityPayload: ActivityPayloadResponse = ActivityPayloadResponse.fromJSON(connectionMessage.textBody);\n                    const turn = this.privTurnStateManager.GetTurn(responseRequestId);\n\n                    // update the conversation Id\n                    if (activityPayload.conversationId) {\n                        const updateAgentConfig = this.agentConfig.get();\n                        updateAgentConfig.botInfo.conversationId = activityPayload.conversationId;\n                        this.agentConfig.set(updateAgentConfig);\n                    }\n\n                    const pullAudioOutputStream: PullAudioOutputStreamImpl = turn.processActivityPayload(activityPayload);\n                    const activity = new ActivityReceivedEventArgs(activityPayload.messagePayload, pullAudioOutputStream);\n                    if (!!this.privDialogServiceConnector.activityReceived) {\n                        try {\n                            this.privDialogServiceConnector.activityReceived(this.privDialogServiceConnector, activity);\n                            /* tslint:disable:no-empty */\n                        } catch (error) {\n                            // Not going to let errors in the event handler\n                            // trip things up.\n                        }\n                    }\n                }\n                break;\n\n            default:\n                break;\n        }\n    }\n\n    // Cancels recognition.\n    protected cancelRecognition(\n        sessionId: string,\n        requestId: string,\n        cancellationReason: CancellationReason,\n        errorCode: CancellationErrorCode,\n        error: string,\n        cancelRecoCallback: (e: SpeechRecognitionResult) => void): void {\n\n            this.terminateMessageLoop = true;\n\n            if (!!this.privDialogRequestSession.isRecognizing) {\n                this.privDialogRequestSession.onStopRecognizing();\n            }\n\n            if (!!this.privDialogServiceConnector.canceled) {\n                const properties: PropertyCollection = new PropertyCollection();\n                properties.setProperty(CancellationErrorCodePropertyName, CancellationErrorCode[errorCode]);\n\n                const cancelEvent: SpeechRecognitionCanceledEventArgs = new SpeechRecognitionCanceledEventArgs(\n                    cancellationReason,\n                    error,\n                    errorCode,\n                    undefined,\n                    sessionId);\n\n                try {\n                    this.privDialogServiceConnector.canceled(this.privDialogServiceConnector, cancelEvent);\n                    /* tslint:disable:no-empty */\n                } catch { }\n\n                if (!!cancelRecoCallback) {\n                    const result: SpeechRecognitionResult = new SpeechRecognitionResult(\n                        undefined, // ResultId\n                        ResultReason.Canceled,\n                        undefined, // Text\n                        undefined, // Druation\n                        undefined, // Offset\n                        error,\n                        undefined, // Json\n                        properties);\n                    try {\n                        cancelRecoCallback(result);\n                        /* tslint:disable:no-empty */\n                    } catch { }\n                }\n            }\n    }\n\n    protected listenOnce = (\n        recoMode: RecognitionMode,\n        successCallback: (e: SpeechRecognitionResult) => void,\n        errorCallback: (e: string) => void\n        ): any => {\n            this.privRecognizerConfig.recognitionMode = recoMode;\n\n            this.privDialogRequestSession.startNewRecognition();\n            this.privDialogRequestSession.listenForServiceTelemetry(this.privDialogAudioSource.events);\n\n            // Start the connection to the service. The promise this will create is stored and will be used by configureConnection().\n            this.dialogConnectImpl();\n\n            this.sendPreAudioMessages();\n\n            this.privSuccessCallback = successCallback;\n\n            return this.privDialogAudioSource\n                .attach(this.privDialogRequestSession.audioNodeId)\n                .continueWithPromise<boolean>((result: PromiseResult<IAudioStreamNode>) => {\n                    let audioNode: ReplayableAudioNode;\n\n                    if (result.isError) {\n                        this.cancelRecognition(this.privDialogRequestSession.sessionId, this.privDialogRequestSession.requestId, CancellationReason.Error, CancellationErrorCode.ConnectionFailure, result.error, successCallback);\n                        return PromiseHelper.fromError<boolean>(result.error);\n                    } else {\n                        audioNode = new ReplayableAudioNode(result.result, this.privDialogAudioSource.format as AudioStreamFormatImpl);\n                        this.privDialogRequestSession.onAudioSourceAttachCompleted(audioNode, false);\n                    }\n\n                    return this.privDialogAudioSource.deviceInfo.onSuccessContinueWithPromise<boolean>((deviceInfo: ISpeechConfigAudioDevice): Promise<boolean> => {\n                        this.privRecognizerConfig.SpeechServiceConfig.Context.audio = { source: deviceInfo };\n\n                        return this.configConnection()\n                            .on((_: IConnection) => {\n                                const sessionStartEventArgs: SessionEventArgs = new SessionEventArgs(this.privDialogRequestSession.sessionId);\n\n                                if (!!this.privRecognizer.sessionStarted) {\n                                    this.privRecognizer.sessionStarted(this.privRecognizer, sessionStartEventArgs);\n                                }\n\n                                const audioSendPromise = this.sendAudio(audioNode);\n\n                                // /* tslint:disable:no-empty */\n                                audioSendPromise.on((_: boolean) => { /*add? return true;*/ }, (error: string) => {\n                                    this.cancelRecognition(this.privDialogRequestSession.sessionId, this.privDialogRequestSession.requestId, CancellationReason.Error, CancellationErrorCode.RuntimeError, error, successCallback);\n                                });\n\n                            }, (error: string) => {\n                                this.cancelRecognition(this.privDialogRequestSession.sessionId, this.privDialogRequestSession.requestId, CancellationReason.Error, CancellationErrorCode.ConnectionFailure, error, successCallback);\n                            }).continueWithPromise<boolean>((result: PromiseResult<IConnection>): Promise<boolean> => {\n                                if (result.isError) {\n                                    return PromiseHelper.fromError(result.error);\n                                } else {\n                                    return PromiseHelper.fromResult<boolean>(true);\n                                }\n                            });\n                    });\n                });\n        }\n\n        protected sendAudio = (\n            audioStreamNode: IAudioStreamNode): Promise<boolean> => {\n            // NOTE: Home-baked promises crash ios safari during the invocation\n            // of the error callback chain (looks like the recursion is way too deep, and\n            // it blows up the stack). The following construct is a stop-gap that does not\n            // bubble the error up the callback chain and hence circumvents this problem.\n            // TODO: rewrite with ES6 promises.\n            const deferred = new Deferred<boolean>();\n\n            // The time we last sent data to the service.\n            let nextSendTime: number = Date.now();\n\n            const audioFormat: AudioStreamFormatImpl = this.privDialogAudioSource.format as AudioStreamFormatImpl;\n\n            // Max amount to send before we start to throttle\n            const fastLaneSizeMs: string = this.privRecognizerConfig.parameters.getProperty(\"SPEECH-TransmitLengthBeforThrottleMs\", \"5000\");\n            const maxSendUnthrottledBytes: number = audioFormat.avgBytesPerSec / 1000 * parseInt(fastLaneSizeMs, 10);\n            const startRecogNumber: number = this.privDialogRequestSession.recogNumber;\n\n            const readAndUploadCycle = () => {\n\n                // If speech is done, stop sending audio.\n                if (!this.privDialogIsDisposed &&\n                    !this.privDialogRequestSession.isSpeechEnded &&\n                    this.privDialogRequestSession.isRecognizing &&\n                    this.privDialogRequestSession.recogNumber === startRecogNumber) {\n                    this.fetchDialogConnection().on((connection: IConnection) => {\n                        audioStreamNode.read().on(\n                            (audioStreamChunk: IStreamChunk<ArrayBuffer>) => {\n                                // we have a new audio chunk to upload.\n                                if (this.privDialogRequestSession.isSpeechEnded) {\n                                    // If service already recognized audio end then don't send any more audio\n                                    deferred.resolve(true);\n                                    return;\n                                }\n\n                                let payload: ArrayBuffer;\n                                let sendDelay: number;\n\n                                if (audioStreamChunk.isEnd) {\n                                    payload = null;\n                                    sendDelay = 0;\n                                } else {\n                                    payload = audioStreamChunk.buffer;\n                                    this.privDialogRequestSession.onAudioSent(payload.byteLength);\n\n                                    if (maxSendUnthrottledBytes >= this.privDialogRequestSession.bytesSent) {\n                                        sendDelay = 0;\n                                    } else {\n                                        sendDelay = Math.max(0, nextSendTime - Date.now());\n                                    }\n                                }\n\n                                // Are we ready to send, or need we delay more?\n                                setTimeout(() => {\n                                    if (payload !== null) {\n                                        nextSendTime = Date.now() + (payload.byteLength * 1000 / (audioFormat.avgBytesPerSec * 2));\n                                    }\n\n                                    const uploaded: Promise<boolean> = connection.send(\n                                        new SpeechConnectionMessage(\n                                            MessageType.Binary, \"audio\", this.privDialogRequestSession.requestId, null, payload));\n\n                                    if (!audioStreamChunk.isEnd) {\n                                        uploaded.continueWith((_: PromiseResult<boolean>) => {\n\n                                            // Regardless of success or failure, schedule the next upload.\n                                            // If the underlying connection was broken, the next cycle will\n                                            // get a new connection and re-transmit missing audio automatically.\n                                            readAndUploadCycle();\n                                        });\n                                    } else {\n                                        // the audio stream has been closed, no need to schedule next\n                                        // read-upload cycle.\n                                        this.privDialogRequestSession.onSpeechEnded();\n                                        deferred.resolve(true);\n                                    }\n                                }, sendDelay);\n                            },\n                            (error: string) => {\n                                if (this.privDialogRequestSession.isSpeechEnded) {\n                                    // For whatever reason, Reject is used to remove queue subscribers inside\n                                    // the Queue.DrainAndDispose invoked from DetachAudioNode down below, which\n                                    // means that sometimes things can be rejected in normal circumstances, without\n                                    // any errors.\n                                    deferred.resolve(true); // TODO: remove the argument, it's is completely meaningless.\n                                } else {\n                                    // Only reject, if there was a proper error.\n                                    deferred.reject(error);\n                                }\n                            });\n                    }, (error: string) => {\n                        deferred.reject(error);\n                    });\n                }\n            };\n\n            readAndUploadCycle();\n\n            return deferred.promise();\n        }\n\n    // Establishes a websocket connection to the end point.\n    private dialogConnectImpl(isUnAuthorized: boolean = false): Promise<IConnection> {\n        if (this.privDialogConnectionPromise) {\n            if (this.privDialogConnectionPromise.result().isCompleted &&\n                (this.privDialogConnectionPromise.result().isError\n                    || this.privDialogConnectionPromise.result().result.state() === ConnectionState.Disconnected)) {\n                this.agentConfigSent = false;\n                this.privDialogConnectionPromise = null;\n            } else {\n                return this.privDialogConnectionPromise;\n            }\n        }\n\n        this.privDialogAuthFetchEventId = createNoDashGuid();\n\n        // keep the connectionId for reconnect events\n        if (this.privConnectionId === undefined) {\n            this.privConnectionId = createNoDashGuid();\n        }\n\n        this.privDialogRequestSession.onPreConnectionStart(this.privDialogAuthFetchEventId, this.privConnectionId);\n\n        const authPromise = isUnAuthorized ? this.privDialogAuthentication.fetchOnExpiry(this.privDialogAuthFetchEventId) : this.privDialogAuthentication.fetch(this.privDialogAuthFetchEventId);\n\n        this.privDialogConnectionPromise = authPromise\n            .continueWithPromise((result: PromiseResult<AuthInfo>) => {\n                if (result.isError) {\n                    this.privDialogRequestSession.onAuthCompleted(true, result.error);\n                    throw new Error(result.error);\n                } else {\n                    this.privDialogRequestSession.onAuthCompleted(false);\n                }\n\n                const connection: IConnection = this.privDialogConnectionFactory.create(this.privRecognizerConfig, result.result, this.privConnectionId);\n\n                this.privDialogRequestSession.listenForServiceTelemetry(connection.events);\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.privDialogRequestSession.onPreConnectionStart(this.privDialogAuthFetchEventId, this.privConnectionId);\n                        this.privDialogRequestSession.onConnectionEstablishCompleted(response.statusCode);\n\n                        return PromiseHelper.fromResult<IConnection>(connection);\n                    } else if (response.statusCode === 403 && !isUnAuthorized) {\n                        return this.dialogConnectImpl(true);\n                    } else {\n                        this.privDialogRequestSession.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.privDialogConnectionPromise;\n    }\n\n    private receiveDialogMessageOverride = (\n        successCallback?: (e: SpeechRecognitionResult) => 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.fetchDialogConnection().on((connection: IConnection): Promise<IConnection> => {\n                return connection.read()\n                    .onSuccessContinueWithPromise((message: ConnectionMessage): Promise<IConnection> => {\n                        const isDisposed: boolean = this.isDisposed();\n                        const terminateMessageLoop = (!this.isDisposed() && this.terminateMessageLoop);\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.receiveDialogMessageOverride();\n                        }\n\n                        const connectionMessage = SpeechConnectionMessage.fromConnectionMessage(message);\n\n                        switch (connectionMessage.path.toLowerCase()) {\n                            case \"turn.start\":\n                                {\n                                    const turnRequestId = connectionMessage.requestId.toUpperCase();\n                                    const audioSessionReqId = this.privDialogRequestSession.requestId.toUpperCase();\n\n                                    // turn started by the service\n                                    if (turnRequestId !== audioSessionReqId) {\n                                        this.privTurnStateManager.StartTurn(turnRequestId);\n                                    }\n                                }\n                                break;\n                            case \"speech.startdetected\":\n                                const speechStartDetected: SpeechDetected = SpeechDetected.fromJSON(connectionMessage.textBody);\n\n                                const speechStartEventArgs = new RecognitionEventArgs(speechStartDetected.Offset, this.privDialogRequestSession.sessionId);\n\n                                if (!!this.privRecognizer.speechStartDetected) {\n                                    this.privRecognizer.speechStartDetected(this.privRecognizer, speechStartEventArgs);\n                                }\n\n                                break;\n                            case \"speech.enddetected\":\n\n                                let json: string;\n\n                                if (connectionMessage.textBody.length > 0) {\n                                    json = connectionMessage.textBody;\n                                } else {\n                                    // If the request was empty, the JSON returned is empty.\n                                    json = \"{ Offset: 0 }\";\n                                }\n\n                                const speechStopDetected: SpeechDetected = SpeechDetected.fromJSON(json);\n\n                                this.privDialogRequestSession.onServiceRecognized(speechStopDetected.Offset + this.privDialogRequestSession.currentTurnAudioOffset);\n\n                                const speechStopEventArgs = new RecognitionEventArgs(speechStopDetected.Offset + this.privDialogRequestSession.currentTurnAudioOffset, this.privDialogRequestSession.sessionId);\n\n                                if (!!this.privRecognizer.speechEndDetected) {\n                                    this.privRecognizer.speechEndDetected(this.privRecognizer, speechStopEventArgs);\n                                }\n                                break;\n\n                            case \"turn.end\":\n                                {\n                                    const turnEndRequestId = connectionMessage.requestId.toUpperCase();\n\n                                    const audioSessionReqId = this.privDialogRequestSession.requestId.toUpperCase();\n\n                                    // turn started by the service\n                                    if (turnEndRequestId !== audioSessionReqId) {\n                                        this.privTurnStateManager.CompleteTurn(turnEndRequestId);\n                                    } else {\n                                        // Audio session turn\n\n                                        const sessionStopEventArgs: SessionEventArgs = new SessionEventArgs(this.privDialogRequestSession.sessionId);\n                                        this.privDialogRequestSession.onServiceTurnEndResponse(false);\n\n                                        if (this.privDialogRequestSession.isSpeechEnded) {\n                                            if (!!this.privRecognizer.sessionStopped) {\n                                                this.privRecognizer.sessionStopped(this.privRecognizer, sessionStopEventArgs);\n                                            }\n                                        }\n                                    }\n                                }\n                                break;\n\n                            default:\n                                this.processTypeSpecificMessages(\n                                    connectionMessage,\n                                    successCallback,\n                                    errorCallBack);\n                        }\n\n                        return this.receiveDialogMessageOverride();\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.receiveDialogMessageOverride();\n\n        return messageRetrievalPromise.on((r: IConnection) => {\n            return true;\n        }, (error: string) => {\n            this.cancelRecognition(this.privDialogRequestSession.sessionId, this.privDialogRequestSession.requestId, CancellationReason.Error, CancellationErrorCode.RuntimeError, error, this.privSuccessCallback);\n        });\n    }\n\n    // Takes an established websocket connection to the endpoint and sends speech configuration information.\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        this.privConnectionConfigPromise = this.dialogConnectImpl().onSuccessContinueWithPromise((connection: IConnection): Promise<IConnection> => {\n            return this.sendSpeechServiceConfig(connection, this.privDialogRequestSession, this.privRecognizerConfig.SpeechServiceConfig.serialize())\n                .onSuccessContinueWithPromise((_: boolean) => {\n                    return this.sendAgentConfig(connection).onSuccessContinueWith((_: boolean) => {\n                        return connection;\n                    });\n                });\n        });\n\n        return this.privConnectionConfigPromise;\n    }\n\n    private fetchDialogConnection = (): Promise<IConnection> => {\n        return this.configConnection();\n    }\n\n    private sendPreAudioMessages(): void {\n        this.fetchDialogConnection().onSuccessContinueWith((connection: IConnection): void => {\n            this.sendAgentContext(connection);\n        });\n    }\n\n    private sendAgentConfig = (connection: IConnection): Promise<boolean> => {\n        if (this.agentConfig && !this.agentConfigSent) {\n            const agentConfigJson = this.agentConfig.toJsonString();\n\n            this.agentConfigSent = true;\n\n            return connection.send(new SpeechConnectionMessage(\n                MessageType.Text,\n                \"agent.config\",\n                this.privDialogRequestSession.requestId,\n                \"application/json\",\n                agentConfigJson));\n        }\n\n        return PromiseHelper.fromResult(true);\n    }\n\n    private sendAgentContext = (connection: IConnection): Promise<boolean> => {\n        const guid: string = createGuid();\n\n        const agentContext: any = {\n            channelData: \"\",\n            context: {\n                interactionId: guid\n            },\n            version: 0.5\n        };\n\n        const agentContextJson = JSON.stringify(agentContext);\n\n        return connection.send(new SpeechConnectionMessage(\n            MessageType.Text,\n            \"speech.agent.context\",\n            this.privDialogRequestSession.requestId,\n            \"application/json\",\n            agentContextJson));\n    }\n\n    private fireEventForResult(serviceResult: SimpleSpeechPhrase, properties: PropertyCollection): SpeechRecognitionEventArgs {\n        const resultReason: ResultReason = EnumTranslation.implTranslateRecognitionResult(serviceResult.RecognitionStatus);\n\n        const offset: number = serviceResult.Offset + this.privDialogRequestSession.currentTurnAudioOffset;\n\n        const result = new SpeechRecognitionResult(\n            this.privDialogRequestSession.requestId,\n            resultReason,\n            serviceResult.DisplayText,\n            serviceResult.Duration,\n            offset,\n            undefined,\n            JSON.stringify(serviceResult),\n            properties);\n\n        const ev = new SpeechRecognitionEventArgs(result, offset, this.privDialogRequestSession.sessionId);\n        return ev;\n    }\n}\n"]}