{"version":3,"sources":["src/common.speech/SynthesisAdapterBase.ts"],"names":[],"mappings":"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","file":"SynthesisAdapterBase.d.ts","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved.\r\n// Licensed under the MIT license.\r\n\r\nimport {\r\n    ArgumentNullError,\r\n    ConnectionClosedEvent,\r\n    ConnectionEvent,\r\n    ConnectionMessage,\r\n    ConnectionState,\r\n    createNoDashGuid,\r\n    EventSource,\r\n    IAudioDestination,\r\n    IConnection,\r\n    IDisposable,\r\n    MessageType,\r\n    ServiceEvent,\r\n} from \"../common/Exports\";\r\nimport { AudioOutputFormatImpl } from \"../sdk/Audio/AudioOutputFormat\";\r\nimport {\r\n    CancellationErrorCode,\r\n    CancellationReason,\r\n    PropertyCollection,\r\n    PropertyId,\r\n    ResultReason,\r\n    SpeechSynthesisBookmarkEventArgs,\r\n    SpeechSynthesisEventArgs,\r\n    SpeechSynthesisResult,\r\n    SpeechSynthesisVisemeEventArgs,\r\n    SpeechSynthesisWordBoundaryEventArgs,\r\n    SpeechSynthesizer,\r\n} from \"../sdk/Exports\";\r\nimport {\r\n    AgentConfig,\r\n    CancellationErrorCodePropertyName,\r\n    ISynthesisConnectionFactory,\r\n    MetadataType,\r\n    SynthesisAudioMetadata,\r\n    SynthesisContext,\r\n    SynthesisTurn,\r\n    SynthesizerConfig\r\n} from \"./Exports\";\r\nimport { AuthInfo, IAuthentication } from \"./IAuthentication\";\r\nimport { SpeechConnectionMessage } from \"./SpeechConnectionMessage.Internal\";\r\n\r\nexport class SynthesisAdapterBase implements IDisposable {\r\n    protected privSynthesisTurn: SynthesisTurn;\r\n    protected privConnectionId: string;\r\n    protected privSynthesizerConfig: SynthesizerConfig;\r\n    protected privSpeechSynthesizer: SpeechSynthesizer;\r\n    protected privSuccessCallback: (e: SpeechSynthesisResult) => void;\r\n    protected privErrorCallback: (e: string) => void;\r\n\r\n    public get synthesisContext(): SynthesisContext {\r\n        return this.privSynthesisContext;\r\n    }\r\n\r\n    public get agentConfig(): AgentConfig {\r\n        return this.privAgentConfig;\r\n    }\r\n\r\n    public get connectionEvents(): EventSource<ConnectionEvent> {\r\n        return this.privConnectionEvents;\r\n    }\r\n\r\n    public get serviceEvents(): EventSource<ServiceEvent> {\r\n        return this.privServiceEvents;\r\n    }\r\n\r\n    protected speakOverride: (ssml: string, requestId: string, sc: (e: SpeechSynthesisResult) => void, ec: (e: string) => void) => any = undefined;\r\n\r\n    // Called when telemetry data is sent to the service.\r\n    // Used for testing Telemetry capture.\r\n    public static telemetryData: (json: string) => void;\r\n    public static telemetryDataEnabled: boolean = true;\r\n\r\n    public set activityTemplate(messagePayload: string) { this.privActivityTemplate = messagePayload; }\r\n    public get activityTemplate(): string { return this.privActivityTemplate; }\r\n\r\n    protected receiveMessageOverride: () => any = undefined;\r\n\r\n    protected connectImplOverride: (isUnAuthorized: boolean) => any = undefined;\r\n\r\n    protected configConnectionOverride: (connection: IConnection) => any = undefined;\r\n\r\n    public set audioOutputFormat(format: AudioOutputFormatImpl) {\r\n        this.privAudioOutputFormat = format;\r\n        this.privSynthesisTurn.audioOutputFormat = format;\r\n        if (this.privSessionAudioDestination !== undefined) {\r\n            this.privSessionAudioDestination.format = format;\r\n        }\r\n        if (this.synthesisContext !== undefined) {\r\n            this.synthesisContext.audioOutputFormat = format;\r\n        }\r\n    }\r\n    private privAuthentication: IAuthentication;\r\n    private privConnectionFactory: ISynthesisConnectionFactory;\r\n\r\n    // A promise for a configured connection.\r\n    // Do not consume directly, call fetchConnection instead.\r\n    private privConnectionConfigurationPromise: Promise<IConnection>;\r\n\r\n    // A promise for a connection, but one that has not had the speech context sent yet.\r\n    // Do not consume directly, call fetchConnection instead.\r\n    private privConnectionPromise: Promise<IConnection>;\r\n    private privAuthFetchEventId: string;\r\n    private privIsDisposed: boolean;\r\n    private privConnectionEvents: EventSource<ConnectionEvent>;\r\n    private privServiceEvents: EventSource<ServiceEvent>;\r\n    private privSynthesisContext: SynthesisContext;\r\n    private privAgentConfig: AgentConfig;\r\n    private privServiceHasSentMessage: boolean;\r\n    private privActivityTemplate: string;\r\n    private privAudioOutputFormat: AudioOutputFormatImpl;\r\n    private privSessionAudioDestination: IAudioDestination;\r\n\r\n    public constructor(\r\n        authentication: IAuthentication,\r\n        connectionFactory: ISynthesisConnectionFactory,\r\n        synthesizerConfig: SynthesizerConfig,\r\n        speechSynthesizer: SpeechSynthesizer,\r\n        audioDestination: IAudioDestination) {\r\n\r\n        if (!authentication) {\r\n            throw new ArgumentNullError(\"authentication\");\r\n        }\r\n\r\n        if (!connectionFactory) {\r\n            throw new ArgumentNullError(\"connectionFactory\");\r\n        }\r\n\r\n        if (!synthesizerConfig) {\r\n            throw new ArgumentNullError(\"synthesizerConfig\");\r\n        }\r\n\r\n        this.privAuthentication = authentication;\r\n        this.privConnectionFactory = connectionFactory;\r\n        this.privSynthesizerConfig = synthesizerConfig;\r\n        this.privIsDisposed = false;\r\n        this.privSpeechSynthesizer = speechSynthesizer;\r\n        this.privSessionAudioDestination = audioDestination;\r\n        this.privSynthesisTurn = new SynthesisTurn();\r\n        this.privConnectionEvents = new EventSource<ConnectionEvent>();\r\n        this.privServiceEvents = new EventSource<ServiceEvent>();\r\n        this.privSynthesisContext = new SynthesisContext(this.privSpeechSynthesizer);\r\n        this.privAgentConfig = new AgentConfig();\r\n\r\n        this.connectionEvents.attach((connectionEvent: ConnectionEvent): void => {\r\n            if (connectionEvent.name === \"ConnectionClosedEvent\") {\r\n                const connectionClosedEvent = connectionEvent as ConnectionClosedEvent;\r\n                if (connectionClosedEvent.statusCode !== 1000) {\r\n                    this.cancelSynthesisLocal(CancellationReason.Error,\r\n                        connectionClosedEvent.statusCode === 1007 ? CancellationErrorCode.BadRequestParameters : CancellationErrorCode.ConnectionFailure,\r\n                        connectionClosedEvent.reason + \" websocket error code: \" + connectionClosedEvent.statusCode);\r\n                }\r\n            }\r\n        });\r\n    }\r\n\r\n    public static addHeader(audio: ArrayBuffer, format: AudioOutputFormatImpl): ArrayBuffer {\r\n        if (!format.hasHeader) {\r\n            return audio;\r\n        }\r\n        format.updateHeader(audio.byteLength);\r\n        const tmp = new Uint8Array(audio.byteLength + format.header.byteLength);\r\n        tmp.set(new Uint8Array(format.header), 0);\r\n        tmp.set(new Uint8Array(audio), format.header.byteLength);\r\n        return tmp.buffer;\r\n    }\r\n\r\n    public isDisposed(): boolean {\r\n        return this.privIsDisposed;\r\n    }\r\n\r\n    public async dispose(reason?: string): Promise<void> {\r\n        this.privIsDisposed = true;\r\n        if (this.privSessionAudioDestination !== undefined) {\r\n            this.privSessionAudioDestination.close();\r\n        }\r\n        if (this.privConnectionConfigurationPromise) {\r\n            const connection: IConnection = await this.privConnectionConfigurationPromise;\r\n            await connection.dispose(reason);\r\n        }\r\n    }\r\n\r\n    public async connect(): Promise<void> {\r\n        await this.connectImpl();\r\n    }\r\n\r\n    public async sendNetworkMessage(path: string, payload: string | ArrayBuffer): Promise<void> {\r\n        const type: MessageType = typeof payload === \"string\" ? MessageType.Text : MessageType.Binary;\r\n        const contentType: string = typeof payload === \"string\" ? \"application/json\" : \"\";\r\n\r\n        const connection: IConnection = await this.fetchConnection();\r\n        return connection.send(new SpeechConnectionMessage(type, path, this.privSynthesisTurn.requestId, contentType, payload));\r\n    }\r\n\r\n    public async Speak(\r\n        text: string,\r\n        isSSML: boolean,\r\n        requestId: string,\r\n        successCallback: (e: SpeechSynthesisResult) => void,\r\n        errorCallBack: (e: string) => void,\r\n        audioDestination: IAudioDestination,\r\n    ): Promise<void> {\r\n\r\n        let ssml: string;\r\n\r\n        if (isSSML) {\r\n            ssml = text;\r\n        } else {\r\n            ssml = this.privSpeechSynthesizer.buildSsml(text);\r\n        }\r\n\r\n        if (this.speakOverride !== undefined) {\r\n            return this.speakOverride(ssml, requestId, successCallback, errorCallBack);\r\n        }\r\n\r\n        this.privSuccessCallback = successCallback;\r\n        this.privErrorCallback = errorCallBack;\r\n\r\n        this.privSynthesisTurn.startNewSynthesis(requestId, text, isSSML, audioDestination);\r\n\r\n        try {\r\n            await this.connectImpl();\r\n            const connection: IConnection = await this.fetchConnection();\r\n            await this.sendSynthesisContext(connection);\r\n            await this.sendSsmlMessage(connection, ssml, requestId);\r\n            const synthesisStartEventArgs: SpeechSynthesisEventArgs = new SpeechSynthesisEventArgs(\r\n                new SpeechSynthesisResult(\r\n                    requestId,\r\n                    ResultReason.SynthesizingAudioStarted,\r\n                )\r\n            );\r\n\r\n            if (!!this.privSpeechSynthesizer.synthesisStarted) {\r\n                this.privSpeechSynthesizer.synthesisStarted(this.privSpeechSynthesizer, synthesisStartEventArgs);\r\n            }\r\n\r\n            const messageRetrievalPromise = this.receiveMessage();\r\n        } catch (e) {\r\n            this.cancelSynthesisLocal(CancellationReason.Error, CancellationErrorCode.ConnectionFailure, e);\r\n            return Promise.reject(e);\r\n        }\r\n    }\r\n\r\n    // Cancels synthesis.\r\n    protected cancelSynthesis(\r\n        requestId: string,\r\n        cancellationReason: CancellationReason,\r\n        errorCode: CancellationErrorCode,\r\n        error: string): void {\r\n        const properties: PropertyCollection = new PropertyCollection();\r\n        properties.setProperty(CancellationErrorCodePropertyName, CancellationErrorCode[errorCode]);\r\n        const result: SpeechSynthesisResult = new SpeechSynthesisResult(\r\n            requestId,\r\n            ResultReason.Canceled,\r\n            undefined,\r\n            error,\r\n            properties\r\n        );\r\n\r\n        if (!!this.privSpeechSynthesizer.SynthesisCanceled) {\r\n            const cancelEvent: SpeechSynthesisEventArgs = new SpeechSynthesisEventArgs(result);\r\n            try {\r\n                this.privSpeechSynthesizer.SynthesisCanceled(this.privSpeechSynthesizer, cancelEvent);\r\n                /* tslint:disable:no-empty */\r\n            } catch { }\r\n        }\r\n\r\n        if (!!this.privSuccessCallback) {\r\n            try {\r\n                this.privSuccessCallback(result);\r\n                /* tslint:disable:no-empty */\r\n            } catch { }\r\n        }\r\n    }\r\n\r\n    // Cancels synthesis.\r\n    protected cancelSynthesisLocal(\r\n        cancellationReason: CancellationReason,\r\n        errorCode: CancellationErrorCode,\r\n        error: string): void {\r\n\r\n        if (!!this.privSynthesisTurn.isSynthesizing) {\r\n            this.privSynthesisTurn.onStopSynthesizing();\r\n\r\n            this.cancelSynthesis(\r\n                this.privSynthesisTurn.requestId,\r\n                cancellationReason,\r\n                errorCode,\r\n                error);\r\n        }\r\n    }\r\n\r\n    protected processTypeSpecificMessages(\r\n        connectionMessage: SpeechConnectionMessage,\r\n        successCallback?: (e: SpeechSynthesisResult) => void,\r\n        errorCallBack?: (e: string) => void): boolean {\r\n        return true;\r\n    }\r\n\r\n    protected async receiveMessage(): Promise<void> {\r\n        try {\r\n            const connection: IConnection = await this.fetchConnection();\r\n            const message: ConnectionMessage = await connection.read();\r\n\r\n            if (this.receiveMessageOverride !== undefined) {\r\n                return this.receiveMessageOverride();\r\n            }\r\n            if (this.privIsDisposed) {\r\n                // We're done.\r\n                return;\r\n            }\r\n\r\n            // indicates we are draining the queue and it came with no message;\r\n            if (!message) {\r\n                if (!this.privSynthesisTurn.isSynthesizing) {\r\n                    return;\r\n                } else {\r\n                    return this.receiveMessage();\r\n                }\r\n            }\r\n\r\n            this.privServiceHasSentMessage = true;\r\n\r\n            const connectionMessage = SpeechConnectionMessage.fromConnectionMessage(message);\r\n\r\n            if (connectionMessage.requestId.toLowerCase() === this.privSynthesisTurn.requestId.toLowerCase()) {\r\n                switch (connectionMessage.path.toLowerCase()) {\r\n                    case \"turn.start\":\r\n                        this.privSynthesisTurn.onServiceTurnStartResponse();\r\n                        break;\r\n                    case \"response\":\r\n                        this.privSynthesisTurn.onServiceResponseMessage(connectionMessage.textBody);\r\n                        break;\r\n                    case \"audio\":\r\n                        if (this.privSynthesisTurn.streamId.toLowerCase() === connectionMessage.streamId.toLowerCase()\r\n                            && !!connectionMessage.binaryBody) {\r\n                            this.privSynthesisTurn.onAudioChunkReceived(connectionMessage.binaryBody);\r\n                            if (!!this.privSpeechSynthesizer.synthesizing) {\r\n                                try {\r\n                                    const audioWithHeader = SynthesisAdapterBase.addHeader(connectionMessage.binaryBody, this.privSynthesisTurn.audioOutputFormat);\r\n                                    const ev: SpeechSynthesisEventArgs = new SpeechSynthesisEventArgs(\r\n                                        new SpeechSynthesisResult(\r\n                                            this.privSynthesisTurn.requestId,\r\n                                            ResultReason.SynthesizingAudio,\r\n                                            audioWithHeader));\r\n                                    this.privSpeechSynthesizer.synthesizing(this.privSpeechSynthesizer, ev);\r\n                                } catch (error) {\r\n                                    // Not going to let errors in the event handler\r\n                                    // trip things up.\r\n                                }\r\n                            }\r\n                            if (this.privSessionAudioDestination !== undefined) {\r\n                                this.privSessionAudioDestination.write(connectionMessage.binaryBody);\r\n                            }\r\n                        }\r\n                        break;\r\n                    case \"audio.metadata\":\r\n                        const metadataList = SynthesisAudioMetadata.fromJSON(connectionMessage.textBody).Metadata;\r\n                        for (const metadata of metadataList) {\r\n                            switch (metadata.Type) {\r\n                                case MetadataType.WordBoundary:\r\n                                    this.privSynthesisTurn.onWordBoundaryEvent(metadata.Data.text.Text);\r\n\r\n                                    const wordBoundaryEventArgs: SpeechSynthesisWordBoundaryEventArgs = new SpeechSynthesisWordBoundaryEventArgs(\r\n                                        metadata.Data.Offset,\r\n                                        metadata.Data.text.Text,\r\n                                        metadata.Data.text.Length,\r\n                                        this.privSynthesisTurn.currentTextOffset);\r\n\r\n                                    if (!!this.privSpeechSynthesizer.wordBoundary) {\r\n                                        try {\r\n                                            this.privSpeechSynthesizer.wordBoundary(this.privSpeechSynthesizer, wordBoundaryEventArgs);\r\n                                        } catch (error) {\r\n                                            // Not going to let errors in the event handler\r\n                                            // trip things up.\r\n                                        }\r\n                                    }\r\n                                    break;\r\n                                case MetadataType.Bookmark:\r\n                                    const bookmarkEventArgs: SpeechSynthesisBookmarkEventArgs = new SpeechSynthesisBookmarkEventArgs(\r\n                                        metadata.Data.Offset,\r\n                                        metadata.Data.Bookmark);\r\n\r\n                                    if (!!this.privSpeechSynthesizer.bookmarkReached) {\r\n                                        try {\r\n                                            this.privSpeechSynthesizer.bookmarkReached(this.privSpeechSynthesizer, bookmarkEventArgs);\r\n                                        } catch (error) {\r\n                                            // Not going to let errors in the event handler\r\n                                            // trip things up.\r\n                                        }\r\n                                    }\r\n                                    break;\r\n                                case MetadataType.Viseme:\r\n\r\n                                    this.privSynthesisTurn.onVisemeMetadataReceived(metadata);\r\n\r\n                                    if (metadata.Data.IsLastAnimation) {\r\n                                        const visemeEventArgs: SpeechSynthesisVisemeEventArgs = new SpeechSynthesisVisemeEventArgs(\r\n                                            metadata.Data.Offset,\r\n                                            metadata.Data.VisemeId,\r\n                                            this.privSynthesisTurn.getAndClearVisemeAnimation());\r\n\r\n                                        if (!!this.privSpeechSynthesizer.visemeReceived) {\r\n                                            try {\r\n                                                this.privSpeechSynthesizer.visemeReceived(this.privSpeechSynthesizer, visemeEventArgs);\r\n                                            } catch (error) {\r\n                                                // Not going to let errors in the event handler\r\n                                                // trip things up.\r\n                                            }\r\n                                        }\r\n                                    }\r\n                                    break;\r\n                            }\r\n                        }\r\n                        break;\r\n                    case \"turn.end\":\r\n                        this.privSynthesisTurn.onServiceTurnEndResponse();\r\n                        let result: SpeechSynthesisResult;\r\n                        try {\r\n                            const audioBuffer: ArrayBuffer = await this.privSynthesisTurn.getAllReceivedAudioWithHeader();\r\n                            result = new SpeechSynthesisResult(\r\n                                this.privSynthesisTurn.requestId,\r\n                                ResultReason.SynthesizingAudioCompleted,\r\n                                audioBuffer\r\n                            );\r\n                            if (!!this.privSuccessCallback) {\r\n                                this.privSuccessCallback(result);\r\n                            }\r\n                        } catch (error) {\r\n                            if (!!this.privErrorCallback) {\r\n                                this.privErrorCallback(error);\r\n                            }\r\n                        }\r\n                        if (this.privSpeechSynthesizer.synthesisCompleted) {\r\n                            try {\r\n                                this.privSpeechSynthesizer.synthesisCompleted(\r\n                                    this.privSpeechSynthesizer,\r\n                                    new SpeechSynthesisEventArgs(result)\r\n                                );\r\n                            } catch (e) {\r\n                                // Not going to let errors in the event handler\r\n                                // trip things up.\r\n                            }\r\n                        }\r\n                        break;\r\n\r\n                    default:\r\n\r\n                        if (!this.processTypeSpecificMessages(connectionMessage)) {\r\n                            // here are some messages that the derived class has not processed, dispatch them to connect class\r\n                            if (!!this.privServiceEvents) {\r\n                                this.serviceEvents.onEvent(new ServiceEvent(connectionMessage.path.toLowerCase(), connectionMessage.textBody));\r\n                            }\r\n                        }\r\n\r\n                }\r\n            }\r\n\r\n            return this.receiveMessage();\r\n\r\n        } catch (e) {\r\n            // TODO: What goes here?\r\n        }\r\n    }\r\n\r\n    protected sendSynthesisContext = (connection: IConnection): Promise<void> => {\r\n        const synthesisContextJson = this.synthesisContext.toJSON();\r\n\r\n        if (synthesisContextJson) {\r\n            return connection.send(new SpeechConnectionMessage(\r\n                MessageType.Text,\r\n                \"synthesis.context\",\r\n                this.privSynthesisTurn.requestId,\r\n                \"application/json\",\r\n                synthesisContextJson));\r\n        }\r\n        return;\r\n    }\r\n\r\n    protected connectImpl(isUnAuthorized: boolean = false): Promise<IConnection> {\r\n        if (this.privConnectionPromise) {\r\n            return this.privConnectionPromise.then((connection: IConnection): Promise<IConnection> => {\r\n                if (connection.state() === ConnectionState.Disconnected) {\r\n                    this.privConnectionId = null;\r\n                    this.privConnectionPromise = null;\r\n                    this.privServiceHasSentMessage = false;\r\n                    return this.connectImpl();\r\n                }\r\n                return this.privConnectionPromise;\r\n            }, (error: string): Promise<IConnection> => {\r\n                this.privConnectionId = null;\r\n                this.privConnectionPromise = null;\r\n                this.privServiceHasSentMessage = false;\r\n                return this.connectImpl();\r\n            });\r\n        }\r\n        this.privAuthFetchEventId = createNoDashGuid();\r\n        this.privConnectionId = createNoDashGuid();\r\n\r\n        this.privSynthesisTurn.onPreConnectionStart(this.privAuthFetchEventId, this.privConnectionId);\r\n\r\n        const authPromise = isUnAuthorized ? this.privAuthentication.fetchOnExpiry(this.privAuthFetchEventId) : this.privAuthentication.fetch(this.privAuthFetchEventId);\r\n\r\n        this.privConnectionPromise = authPromise.then(async (result: AuthInfo) => {\r\n            await this.privSynthesisTurn.onAuthCompleted(false);\r\n\r\n            const connection: IConnection = this.privConnectionFactory.create(this.privSynthesizerConfig, result, this.privConnectionId);\r\n\r\n            // Attach to the underlying event. No need to hold onto the detach pointers as in the event the connection goes away,\r\n            // it'll stop sending events.\r\n            connection.events.attach((event: ConnectionEvent) => {\r\n                this.connectionEvents.onEvent(event);\r\n            });\r\n            const response = await connection.open();\r\n            if (response.statusCode === 200) {\r\n                await this.privSynthesisTurn.onConnectionEstablishCompleted(response.statusCode);\r\n                return Promise.resolve(connection);\r\n            } else if (response.statusCode === 403 && !isUnAuthorized) {\r\n                return this.connectImpl(true);\r\n            } else {\r\n                await this.privSynthesisTurn.onConnectionEstablishCompleted(response.statusCode, response.reason);\r\n                return Promise.reject(`Unable to contact server. StatusCode: ${response.statusCode}, ${this.privSynthesizerConfig.parameters.getProperty(PropertyId.SpeechServiceConnection_Endpoint)} Reason: ${response.reason}`);\r\n            }\r\n        }, async (error: string): Promise<IConnection> => {\r\n            await this.privSynthesisTurn.onAuthCompleted(true, error);\r\n            throw new Error(error);\r\n        });\r\n\r\n        // Attach an empty handler to allow the promise to run in the background while\r\n        // other startup events happen. It'll eventually be awaited on.\r\n        this.privConnectionPromise.catch(() => { });\r\n\r\n        return this.privConnectionPromise;\r\n    }\r\n    protected sendSpeechServiceConfig = (connection: IConnection, SpeechServiceConfigJson: string): Promise<void> => {\r\n        if (SpeechServiceConfigJson) {\r\n            return connection.send(new SpeechConnectionMessage(\r\n                MessageType.Text,\r\n                \"speech.config\",\r\n                this.privSynthesisTurn.requestId,\r\n                \"application/json\",\r\n                SpeechServiceConfigJson));\r\n        }\r\n    }\r\n\r\n    protected sendSsmlMessage = (connection: IConnection, ssml: string, requestId: string): Promise<void> => {\r\n        return connection.send(new SpeechConnectionMessage(\r\n            MessageType.Text,\r\n            \"ssml\",\r\n            requestId,\r\n            \"application/ssml+xml\",\r\n            ssml));\r\n    }\r\n\r\n    private async fetchConnection(): Promise<IConnection> {\r\n        if (this.privConnectionConfigurationPromise) {\r\n            return this.privConnectionConfigurationPromise.then((connection: IConnection): Promise<IConnection> => {\r\n                if (connection.state() === ConnectionState.Disconnected) {\r\n                    this.privConnectionId = null;\r\n                    this.privConnectionConfigurationPromise = null;\r\n                    this.privServiceHasSentMessage = false;\r\n                    return this.fetchConnection();\r\n                }\r\n                return this.privConnectionConfigurationPromise;\r\n            }, (error: string): Promise<IConnection> => {\r\n                this.privConnectionId = null;\r\n                this.privConnectionConfigurationPromise = null;\r\n                this.privServiceHasSentMessage = false;\r\n                return this.fetchConnection();\r\n            });\r\n        }\r\n        this.privConnectionConfigurationPromise = this.configureConnection();\r\n        return await this.privConnectionConfigurationPromise;\r\n    }\r\n\r\n    // Takes an established websocket connection to the endpoint and sends speech configuration information.\r\n    private async configureConnection(): Promise<IConnection> {\r\n        const connection: IConnection = await this.connectImpl();\r\n        if (this.configConnectionOverride !== undefined) {\r\n            return this.configConnectionOverride(connection);\r\n        }\r\n        await this.sendSpeechServiceConfig(connection, this.privSynthesizerConfig.SpeechServiceConfig.serialize());\r\n        return connection;\r\n    }\r\n}\r\n"]}