@euirim/microsoft-cognitiveservices-speech-sdk
Version:
Microsoft Cognitive Services Speech SDK for JavaScript
1 lines • 39.9 kB
Source Map (JSON)
{"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"]}