UNPKG

@euirim/microsoft-cognitiveservices-speech-sdk

Version:
1 lines 35.3 kB
{"version":3,"sources":["src/common.speech/ServiceRecognizerBase.ts"],"names":[],"mappings":"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","file":"ServiceRecognizerBase.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 ArgumentNullError,\n ConnectionClosedEvent,\n ConnectionEvent,\n ConnectionMessage,\n ConnectionOpenResponse,\n ConnectionState,\n createGuid,\n createNoDashGuid,\n Deferred,\n EventSource,\n IAudioSource,\n IAudioStreamNode,\n IConnection,\n IDisposable,\n IStreamChunk,\n MessageType,\n Promise,\n PromiseHelper,\n PromiseResult,\n} from \"../common/Exports\";\nimport { AudioStreamFormatImpl } from \"../sdk/Audio/AudioStreamFormat\";\nimport {\n CancellationErrorCode,\n CancellationReason,\n PropertyId,\n RecognitionEventArgs,\n Recognizer,\n SessionEventArgs,\n SpeechRecognitionResult,\n} from \"../sdk/Exports\";\nimport {\n AgentConfig,\n DynamicGrammarBuilder,\n ISpeechConfigAudio,\n ISpeechConfigAudioDevice,\n RecognitionMode,\n RequestSession,\n SpeechContext,\n SpeechDetected,\n} from \"./Exports\";\nimport {\n AuthInfo,\n IAuthentication,\n} from \"./IAuthentication\";\nimport { IConnectionFactory } from \"./IConnectionFactory\";\nimport { RecognizerConfig } from \"./RecognizerConfig\";\nimport { SpeechConnectionMessage } from \"./SpeechConnectionMessage.Internal\";\n\nexport abstract class ServiceRecognizerBase implements IDisposable {\n private privAuthentication: IAuthentication;\n private privConnectionFactory: IConnectionFactory;\n private privAudioSource: IAudioSource;\n private privSpeechServiceConfigConnectionId: string;\n\n // A promise for a configured connection.\n // Do not consume directly, call fetchConnection instead.\n private privConnectionConfigurationPromise: 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 fetchConnection instead.\n private privConnectionPromise: Promise<IConnection>;\n private privAuthFetchEventId: string;\n private privIsDisposed: boolean;\n private privMustReportEndOfStream: boolean;\n private privConnectionEvents: EventSource<ConnectionEvent>;\n private privSpeechContext: SpeechContext;\n private privDynamicGrammar: DynamicGrammarBuilder;\n private privAgentConfig: AgentConfig;\n protected privRequestSession: RequestSession;\n protected privConnectionId: string;\n protected privRecognizerConfig: RecognizerConfig;\n protected privRecognizer: Recognizer;\n\n public constructor(\n authentication: IAuthentication,\n connectionFactory: IConnectionFactory,\n audioSource: IAudioSource,\n recognizerConfig: RecognizerConfig,\n recognizer: Recognizer) {\n\n if (!authentication) {\n throw new ArgumentNullError(\"authentication\");\n }\n\n if (!connectionFactory) {\n throw new ArgumentNullError(\"connectionFactory\");\n }\n\n if (!audioSource) {\n throw new ArgumentNullError(\"audioSource\");\n }\n\n if (!recognizerConfig) {\n throw new ArgumentNullError(\"recognizerConfig\");\n }\n\n this.privMustReportEndOfStream = false;\n this.privAuthentication = authentication;\n this.privConnectionFactory = connectionFactory;\n this.privAudioSource = audioSource;\n this.privRecognizerConfig = recognizerConfig;\n this.privIsDisposed = false;\n this.privRecognizer = recognizer;\n this.privRequestSession = new RequestSession(this.privAudioSource.id());\n this.privConnectionEvents = new EventSource<ConnectionEvent>();\n this.privDynamicGrammar = new DynamicGrammarBuilder();\n this.privSpeechContext = new SpeechContext(this.privDynamicGrammar);\n this.privAgentConfig = new AgentConfig();\n }\n\n public get audioSource(): IAudioSource {\n return this.privAudioSource;\n }\n\n public get speechContext(): SpeechContext {\n return this.privSpeechContext;\n }\n\n public get dynamicGrammar(): DynamicGrammarBuilder {\n return this.privDynamicGrammar;\n }\n\n public get agentConfig(): AgentConfig {\n return this.privAgentConfig;\n }\n\n public isDisposed(): boolean {\n return this.privIsDisposed;\n }\n\n public dispose(reason?: string): void {\n this.privIsDisposed = true;\n if (this.privConnectionConfigurationPromise) {\n this.privConnectionConfigurationPromise.onSuccessContinueWith((connection: IConnection) => {\n connection.dispose(reason);\n });\n }\n }\n\n public get connectionEvents(): EventSource<ConnectionEvent> {\n return this.privConnectionEvents;\n }\n\n public get recognitionMode(): RecognitionMode {\n return this.privRecognizerConfig.recognitionMode;\n }\n\n protected recognizeOverride: (recoMode: RecognitionMode, sc: (e: SpeechRecognitionResult) => void, ec: (e: string) => void) => any = undefined;\n\n public recognize(\n recoMode: RecognitionMode,\n successCallback: (e: SpeechRecognitionResult) => void,\n errorCallBack: (e: string) => void,\n ): Promise<boolean> {\n\n if (this.recognizeOverride !== undefined) {\n return this.recognizeOverride(recoMode, successCallback, errorCallBack);\n }\n\n // Clear the existing configuration promise to force a re-transmission of config and context.\n this.privConnectionConfigurationPromise = null;\n this.privRecognizerConfig.recognitionMode = recoMode;\n\n this.privRequestSession.startNewRecognition();\n this.privRequestSession.listenForServiceTelemetry(this.privAudioSource.events);\n\n // Start the connection to the service. The promise this will create is stored and will be used by configureConnection().\n this.connectImpl();\n\n return this.audioSource\n .attach(this.privRequestSession.audioNodeId)\n .continueWithPromise<boolean>((result: PromiseResult<IAudioStreamNode>) => {\n let audioNode: ReplayableAudioNode;\n\n if (result.isError) {\n this.cancelRecognitionLocal(CancellationReason.Error, CancellationErrorCode.ConnectionFailure, result.error, successCallback);\n return PromiseHelper.fromError<boolean>(result.error);\n } else {\n audioNode = new ReplayableAudioNode(result.result, this.audioSource.format as AudioStreamFormatImpl);\n this.privRequestSession.onAudioSourceAttachCompleted(audioNode, false);\n }\n\n return this.audioSource.deviceInfo.onSuccessContinueWithPromise<boolean>((deviceInfo: ISpeechConfigAudioDevice): Promise<boolean> => {\n this.privRecognizerConfig.SpeechServiceConfig.Context.audio = { source: deviceInfo };\n\n return this.configureConnection()\n .on((_: IConnection) => {\n const sessionStartEventArgs: SessionEventArgs = new SessionEventArgs(this.privRequestSession.sessionId);\n\n if (!!this.privRecognizer.sessionStarted) {\n this.privRecognizer.sessionStarted(this.privRecognizer, sessionStartEventArgs);\n }\n\n const messageRetrievalPromise = this.receiveMessage(successCallback, errorCallBack);\n const audioSendPromise = this.sendAudio(audioNode);\n\n /* tslint:disable:no-empty */\n audioSendPromise.on((_: boolean) => { }, (error: string) => {\n this.cancelRecognitionLocal(CancellationReason.Error, CancellationErrorCode.RuntimeError, error, successCallback);\n });\n\n const completionPromise = PromiseHelper.whenAll([messageRetrievalPromise, audioSendPromise]);\n\n return completionPromise.on((r: boolean) => {\n return true;\n }, (error: string) => {\n this.cancelRecognitionLocal(CancellationReason.Error, CancellationErrorCode.RuntimeError, error, successCallback);\n });\n\n }, (error: string) => {\n this.cancelRecognitionLocal(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 public stopRecognizing(): void {\n if (this.privRequestSession.isRecognizing) {\n this.privRequestSession.onStopRecognizing();\n this.sendTelemetryData();\n this.audioSource.turnOff();\n this.sendFinalAudio();\n this.privRequestSession.dispose();\n }\n }\n\n public connect(): void {\n this.connectImpl().result();\n }\n\n protected disconnectOverride: () => any = undefined;\n\n public disconnect(): void {\n if (this.disconnectOverride !== undefined) {\n this.disconnectOverride();\n return;\n }\n\n this.cancelRecognitionLocal(CancellationReason.Error,\n CancellationErrorCode.NoError,\n \"Disconnecting\",\n undefined);\n\n if (this.privConnectionPromise.result().isCompleted) {\n if (!this.privConnectionPromise.result().isError) {\n this.privConnectionPromise.result().result.dispose();\n this.privConnectionPromise = null;\n }\n } else {\n this.privConnectionPromise.onSuccessContinueWith((connection: IConnection) => {\n connection.dispose();\n });\n }\n }\n\n // Called when telemetry data is sent to the service.\n // Used for testing Telemetry capture.\n public static telemetryData: (json: string) => void;\n public static telemetryDataEnabled: boolean = true;\n\n public sendMessage(message: string): void {}\n\n protected abstract processTypeSpecificMessages(\n connectionMessage: SpeechConnectionMessage,\n successCallback?: (e: SpeechRecognitionResult) => void,\n errorCallBack?: (e: string) => void): void;\n\n protected sendTelemetryData = () => {\n const telemetryData = this.privRequestSession.getTelemetry();\n // console.warn(\"Telem: \" + telemetryData);\n if (ServiceRecognizerBase.telemetryDataEnabled !== true ||\n this.privIsDisposed ||\n null === telemetryData) {\n return PromiseHelper.fromResult(true);\n }\n\n if (!!ServiceRecognizerBase.telemetryData) {\n try {\n ServiceRecognizerBase.telemetryData(telemetryData);\n /* tslint:disable:no-empty */\n } catch { }\n }\n\n return this.fetchConnection().onSuccessContinueWith((connection: IConnection): Promise<boolean> => {\n return connection.send(new SpeechConnectionMessage(\n MessageType.Text,\n \"telemetry\",\n this.privRequestSession.requestId,\n \"application/json\",\n telemetryData));\n });\n }\n\n // Cancels recognition.\n protected abstract cancelRecognition(\n sessionId: string,\n requestId: string,\n cancellationReason: CancellationReason,\n errorCode: CancellationErrorCode,\n error: string,\n cancelRecoCallback: (r: SpeechRecognitionResult) => void): void;\n\n // Cancels recognition.\n protected cancelRecognitionLocal(\n cancellationReason: CancellationReason,\n errorCode: CancellationErrorCode,\n error: string,\n cancelRecoCallback: (r: SpeechRecognitionResult) => void): void {\n\n if (!!this.privRequestSession.isRecognizing) {\n this.privRequestSession.onStopRecognizing();\n this.sendTelemetryData();\n\n this.cancelRecognition(\n this.privRequestSession.sessionId,\n this.privRequestSession.requestId,\n cancellationReason,\n errorCode,\n error,\n cancelRecoCallback);\n }\n }\n\n protected receiveMessageOverride: (sc?: (e: SpeechRecognitionResult) => void, ec?: (e: string) => void) => any = undefined;\n\n protected receiveMessage = (\n successCallback: (e: SpeechRecognitionResult) => void,\n errorCallBack: (e: string) => void,\n ): Promise<IConnection> => {\n return this.fetchConnection().on((connection: IConnection): Promise<IConnection> => {\n return connection.read()\n .onSuccessContinueWithPromise((message: ConnectionMessage) => {\n if (this.receiveMessageOverride !== undefined) {\n return this.receiveMessageOverride();\n }\n if (this.privIsDisposed || !this.privRequestSession.isRecognizing) {\n // We're done.\n return PromiseHelper.fromResult(undefined);\n }\n\n // indicates we are draining the queue and it came with no message;\n if (!message) {\n if (!this.privRequestSession.isRecognizing) {\n return PromiseHelper.fromResult(true);\n } else {\n return this.receiveMessage(successCallback, errorCallBack);\n }\n }\n\n const connectionMessage = SpeechConnectionMessage.fromConnectionMessage(message);\n\n if (connectionMessage.requestId.toLowerCase() === this.privRequestSession.requestId.toLowerCase()) {\n switch (connectionMessage.path.toLowerCase()) {\n case \"turn.start\":\n this.privMustReportEndOfStream = true;\n break;\n case \"speech.startdetected\":\n const speechStartDetected: SpeechDetected = SpeechDetected.fromJSON(connectionMessage.textBody);\n\n const speechStartEventArgs = new RecognitionEventArgs(speechStartDetected.Offset, this.privRequestSession.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 // Only shrink the buffers for continuous recognition.\n // For single shot, the speech.phrase message will come after the speech.end and it should own buffer shrink.\n if (this.privRecognizerConfig.isContinuousRecognition) {\n this.privRequestSession.onServiceRecognized(speechStopDetected.Offset + this.privRequestSession.currentTurnAudioOffset);\n }\n\n const speechStopEventArgs = new RecognitionEventArgs(speechStopDetected.Offset + this.privRequestSession.currentTurnAudioOffset, this.privRequestSession.sessionId);\n\n if (!!this.privRecognizer.speechEndDetected) {\n this.privRecognizer.speechEndDetected(this.privRecognizer, speechStopEventArgs);\n }\n break;\n case \"turn.end\":\n this.sendTelemetryData();\n\n if (this.privRequestSession.isSpeechEnded && this.privMustReportEndOfStream) {\n this.privMustReportEndOfStream = false;\n this.cancelRecognitionLocal(CancellationReason.EndOfStream, CancellationErrorCode.NoError, undefined, successCallback);\n }\n\n const sessionStopEventArgs: SessionEventArgs = new SessionEventArgs(this.privRequestSession.sessionId);\n this.privRequestSession.onServiceTurnEndResponse(this.privRecognizerConfig.isContinuousRecognition);\n\n if (!this.privRecognizerConfig.isContinuousRecognition || this.privRequestSession.isSpeechEnded) {\n if (!!this.privRecognizer.sessionStopped) {\n this.privRecognizer.sessionStopped(this.privRecognizer, sessionStopEventArgs);\n }\n\n return PromiseHelper.fromResult(true);\n } else {\n this.fetchConnection().onSuccessContinueWith((connection: IConnection) => {\n this.sendSpeechContext(connection);\n });\n }\n default:\n this.processTypeSpecificMessages(\n connectionMessage,\n successCallback,\n errorCallBack);\n }\n }\n\n return this.receiveMessage(successCallback, errorCallBack);\n });\n }, (error: string) => {\n });\n }\n\n protected sendSpeechContext = (connection: IConnection): Promise<boolean> => {\n const speechContextJson = this.speechContext.toJSON();\n\n if (speechContextJson) {\n return connection.send(new SpeechConnectionMessage(\n MessageType.Text,\n \"speech.context\",\n this.privRequestSession.requestId,\n \"application/json\",\n speechContextJson));\n }\n return PromiseHelper.fromResult(true);\n }\n\n protected connectImplOverride: (isUnAuthorized: boolean) => any = undefined;\n\n // Establishes a websocket connection to the end point.\n protected connectImpl(isUnAuthorized: boolean = false): Promise<IConnection> {\n\n if (this.connectImplOverride !== undefined) {\n return this.connectImplOverride(isUnAuthorized);\n }\n\n if (this.privConnectionPromise) {\n if (this.privConnectionPromise.result().isCompleted &&\n (this.privConnectionPromise.result().isError\n || this.privConnectionPromise.result().result.state() === ConnectionState.Disconnected)) {\n this.privConnectionId = null;\n this.privConnectionPromise = null;\n return this.connectImpl();\n } else {\n return this.privConnectionPromise;\n }\n }\n\n this.privAuthFetchEventId = createNoDashGuid();\n this.privConnectionId = createNoDashGuid();\n\n this.privRequestSession.onPreConnectionStart(this.privAuthFetchEventId, this.privConnectionId);\n\n const authPromise = isUnAuthorized ? this.privAuthentication.fetchOnExpiry(this.privAuthFetchEventId) : this.privAuthentication.fetch(this.privAuthFetchEventId);\n\n this.privConnectionPromise = authPromise\n .continueWithPromise((result: PromiseResult<AuthInfo>) => {\n if (result.isError) {\n this.privRequestSession.onAuthCompleted(true, result.error);\n throw new Error(result.error);\n } else {\n this.privRequestSession.onAuthCompleted(false);\n }\n\n const connection: IConnection = this.privConnectionFactory.create(this.privRecognizerConfig, result.result, this.privConnectionId);\n\n this.privRequestSession.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.privRequestSession.onPreConnectionStart(this.privAuthFetchEventId, this.privConnectionId);\n this.privRequestSession.onConnectionEstablishCompleted(response.statusCode);\n\n return PromiseHelper.fromResult<IConnection>(connection);\n } else if (response.statusCode === 403 && !isUnAuthorized) {\n return this.connectImpl(true);\n } else {\n this.privRequestSession.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 return this.privConnectionPromise;\n }\n\n protected configConnectionOverride: () => any = undefined;\n\n protected fetchConnectionOverride: () => any = undefined;\n\n protected sendSpeechServiceConfig = (connection: IConnection, requestSession: RequestSession, SpeechServiceConfigJson: string): Promise<boolean> => {\n // filter out anything that is not required for the service to work.\n if (ServiceRecognizerBase.telemetryDataEnabled !== true) {\n const withTelemetry = JSON.parse(SpeechServiceConfigJson);\n\n const replacement: any = {\n context: {\n system: withTelemetry.context.system,\n },\n };\n\n SpeechServiceConfigJson = JSON.stringify(replacement);\n }\n\n if (SpeechServiceConfigJson) { // && this.privConnectionId !== this.privSpeechServiceConfigConnectionId) {\n this.privSpeechServiceConfigConnectionId = this.privConnectionId;\n return connection.send(new SpeechConnectionMessage(\n MessageType.Text,\n \"speech.config\",\n requestSession.requestId,\n \"application/json\",\n SpeechServiceConfigJson));\n }\n\n return PromiseHelper.fromResult(true);\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.privAudioSource.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.privRequestSession.recogNumber;\n\n const readAndUploadCycle = () => {\n\n // If speech is done, stop sending audio.\n if (!this.privIsDisposed &&\n !this.privRequestSession.isSpeechEnded &&\n this.privRequestSession.isRecognizing &&\n this.privRequestSession.recogNumber === startRecogNumber) {\n this.fetchConnection().on((connection: IConnection) => {\n audioStreamNode.read().on(\n (audioStreamChunk: IStreamChunk<ArrayBuffer>) => {\n // we have a new audio chunk to upload.\n if (this.privRequestSession.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.privRequestSession.onAudioSent(payload.byteLength);\n\n if (maxSendUnthrottledBytes >= this.privRequestSession.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.privRequestSession.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.privRequestSession.onSpeechEnded();\n deferred.resolve(true);\n }\n }, sendDelay);\n },\n (error: string) => {\n if (this.privRequestSession.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 private sendFinalAudio(): Promise<boolean> {\n const deferred = new Deferred<boolean>();\n\n this.fetchConnection().on((connection: IConnection) => {\n connection.send(new SpeechConnectionMessage(MessageType.Binary, \"audio\", this.privRequestSession.requestId, null, null)).on((_: boolean) => {\n deferred.resolve(true);\n }, (error: string) => {\n deferred.reject(error);\n });\n }, (error: string) => {\n deferred.reject(error);\n });\n\n return deferred.promise();\n }\n\n private fetchConnection = (): Promise<IConnection> => {\n if (this.fetchConnectionOverride !== undefined) {\n return this.fetchConnectionOverride();\n }\n\n return this.configureConnection();\n }\n\n // Takes an established websocket connection to the endpoint and sends speech configuration information.\n private configureConnection(): Promise<IConnection> {\n if (this.configConnectionOverride !== undefined) {\n return this.configConnectionOverride();\n }\n\n if (this.privConnectionConfigurationPromise) {\n if (this.privConnectionConfigurationPromise.result().isCompleted &&\n (this.privConnectionConfigurationPromise.result().isError\n || this.privConnectionConfigurationPromise.result().result.state() === ConnectionState.Disconnected)) {\n\n this.privConnectionConfigurationPromise = null;\n return this.configureConnection();\n } else {\n return this.privConnectionConfigurationPromise;\n }\n }\n\n this.privConnectionConfigurationPromise = this.connectImpl().onSuccessContinueWithPromise((connection: IConnection): Promise<IConnection> => {\n return this.sendSpeechServiceConfig(connection, this.privRequestSession, this.privRecognizerConfig.SpeechServiceConfig.serialize())\n .onSuccessContinueWithPromise((_: boolean) => {\n return this.sendSpeechContext(connection).onSuccessContinueWith((_: boolean) => {\n return connection;\n });\n });\n });\n\n return this.privConnectionConfigurationPromise;\n }\n}\n"]}