@hahnpro/ms-speech-sdk
Version:
Microsoft Cognitive Services Speech SDK for JavaScript
1 lines • 18.1 kB
Source Map (JSON)
{"version":3,"sources":["src/common.browser/WebsocketMessageAdapter.ts"],"names":[],"mappings":"AAaA,OAAO,EAMH,eAAe,EACf,iBAAiB,EAGjB,sBAAsB,EAGtB,eAAe,EAGf,WAAW,EACX,0BAA0B,EAI7B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAQ3C,qBAAa,uBAAuB;IAChC,OAAO,CAAC,mBAAmB,CAAkB;IAC7C,OAAO,CAAC,oBAAoB,CAA6B;IACzD,OAAO,CAAC,mBAAmB,CAAY;IAEvC,OAAO,CAAC,oBAAoB,CAAmB;IAC/C,OAAO,CAAC,yBAAyB,CAA2B;IAC5D,OAAO,CAAC,+BAA+B,CAAmC;IAC1E,OAAO,CAAC,gCAAgC,CAAiB;IACzD,OAAO,CAAC,sBAAsB,CAAiB;IAC/C,OAAO,CAAC,oBAAoB,CAA+B;IAC3D,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,OAAO,CAAS;IAExB,OAAO,CAAC,WAAW,CAA4B;IAC/C,OAAO,CAAC,qBAAqB,CAAS;IACtC,OAAO,CAAC,qBAAqB,CAAU;IAEvC,OAAc,iBAAiB,EAAE,OAAO,CAAS;gBAG7C,GAAG,EAAE,MAAM,EACX,YAAY,EAAE,MAAM,EACpB,gBAAgB,EAAE,0BAA0B,EAC5C,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,EAClC,iBAAiB,EAAE,OAAO;IA0B9B,IAAW,KAAK,IAAI,eAAe,CAElC;IAEM,IAAI,IAAI,OAAO,CAAC,sBAAsB,CAAC;IAkIvC,IAAI,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAyB/C,IAAI,IAAI,OAAO,CAAC,iBAAiB,CAAC;IAQlC,KAAK,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAY5C,IAAW,MAAM,IAAI,WAAW,CAAC,eAAe,CAAC,CAEhD;IAED,OAAO,CAAC,cAAc;YAuBR,OAAO;YAcP,gBAAgB;IAkB9B,OAAO,CAAC,OAAO;IAuCf,OAAO,KAAK,eAAe,GAE1B;CAEJ","file":"WebsocketMessageAdapter.d.ts","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved.\n// Licensed under the MIT license.\n\n// Node.JS specific web socket / browser support.\n// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// import * as http from \"http\";\n// import * as net from \"net\";\n// import * as tls from \"tls\";\n// import * as https from \"https-proxy-agent\";\n// import Agent from \"agent-base\";\n\n// import ws from \"ws\";\nimport { HeaderNames } from \"../common.speech/HeaderNames.js\";\nimport {\n ArgumentNullError,\n BackgroundEvent,\n ConnectionClosedEvent,\n ConnectionErrorEvent,\n ConnectionEstablishedEvent,\n ConnectionEvent,\n ConnectionMessage,\n ConnectionMessageReceivedEvent,\n ConnectionMessageSentEvent,\n ConnectionOpenResponse,\n ConnectionRedirectEvent,\n ConnectionStartEvent,\n ConnectionState,\n Deferred,\n Events,\n EventSource,\n IWebsocketMessageFormatter,\n MessageType,\n Queue,\n RawWebsocketMessage,\n} from \"../common/Exports.js\";\nimport { ProxyInfo } from \"./ProxyInfo.js\";\n\ninterface ISendItem {\n Message: ConnectionMessage;\n RawWebsocketMessage: RawWebsocketMessage;\n sendStatusDeferral: Deferred<void>;\n}\n\nexport class WebsocketMessageAdapter {\n private privConnectionState: ConnectionState;\n private privMessageFormatter: IWebsocketMessageFormatter;\n private privWebsocketClient: WebSocket;\n\n private privSendMessageQueue: Queue<ISendItem>;\n private privReceivingMessageQueue: Queue<ConnectionMessage>;\n private privConnectionEstablishDeferral: Deferred<ConnectionOpenResponse>;\n private privCertificateValidatedDeferral: Deferred<void>;\n private privDisconnectDeferral: Deferred<void>;\n private privConnectionEvents: EventSource<ConnectionEvent>;\n private privConnectionId: string;\n private privUri: string;\n // private proxyInfo: ProxyInfo;\n private privHeaders: { [key: string]: string };\n private privLastErrorReceived: string;\n private privEnableCompression: boolean;\n\n public static forceNpmWebSocket: boolean = false;\n\n public constructor(\n uri: string,\n connectionId: string,\n messageFormatter: IWebsocketMessageFormatter,\n proxyInfo: ProxyInfo,\n headers: { [key: string]: string },\n enableCompression: boolean) {\n\n if (!uri) {\n throw new ArgumentNullError(\"uri\");\n }\n\n if (!messageFormatter) {\n throw new ArgumentNullError(\"messageFormatter\");\n }\n\n // this.proxyInfo = proxyInfo;\n this.privConnectionEvents = new EventSource<ConnectionEvent>();\n this.privConnectionId = connectionId;\n this.privMessageFormatter = messageFormatter;\n this.privConnectionState = ConnectionState.None;\n this.privUri = uri;\n this.privHeaders = headers;\n this.privEnableCompression = enableCompression;\n\n // Add the connection ID to the headers\n this.privHeaders[HeaderNames.ConnectionId] = this.privConnectionId;\n this.privHeaders.connectionId = this.privConnectionId;\n\n this.privLastErrorReceived = \"\";\n }\n\n public get state(): ConnectionState {\n return this.privConnectionState;\n }\n\n public open(): Promise<ConnectionOpenResponse> {\n if (this.privConnectionState === ConnectionState.Disconnected) {\n return Promise.reject<ConnectionOpenResponse>(`Cannot open a connection that is in ${this.privConnectionState} state`);\n }\n\n if (this.privConnectionEstablishDeferral) {\n return this.privConnectionEstablishDeferral.promise;\n }\n\n this.privConnectionEstablishDeferral = new Deferred<ConnectionOpenResponse>();\n this.privCertificateValidatedDeferral = new Deferred<void>();\n\n this.privConnectionState = ConnectionState.Connecting;\n\n try {\n\n // if (typeof WebSocket !== \"undefined\" && !WebsocketMessageAdapter.forceNpmWebSocket) {\n // Browser handles cert checks.\n this.privCertificateValidatedDeferral.resolve();\n\n this.privWebsocketClient = new WebSocket(this.privUri);\n // } else {\n // // Workaround for https://github.com/microsoft/cognitive-services-speech-sdk-js/issues/465\n // // Which is root caused by https://github.com/TooTallNate/node-agent-base/issues/61\n // const uri = new URL(this.privUri);\n // let protocol: string = uri.protocol;\n\n // if (protocol?.toLocaleLowerCase() === \"wss:\") {\n // protocol = \"https:\";\n // } else if (protocol?.toLocaleLowerCase() === \"ws:\") {\n // protocol = \"http:\";\n // }\n\n // const options: ws.ClientOptions = { headers: this.privHeaders, perMessageDeflate: this.privEnableCompression, followRedirects: protocol.toLocaleLowerCase() === \"https:\" };\n // // The ocsp library will handle validation for us and fail the connection if needed.\n // this.privCertificateValidatedDeferral.resolve();\n\n // options.agent = this.getAgent();\n\n // // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n // (options.agent as any).protocol = protocol;\n // this.privWebsocketClient = new ws(this.privUri, options);\n // this.privWebsocketClient.on(\"redirect\", (redirectUrl: string): void => {\n // const event: ConnectionRedirectEvent = new ConnectionRedirectEvent(this.privConnectionId, redirectUrl, this.privUri, `Getting redirect URL from endpoint ${this.privUri} with redirect URL '${redirectUrl}'`);\n // Events.instance.onEvent(event);\n // });\n // }\n\n this.privWebsocketClient.binaryType = \"arraybuffer\";\n this.privReceivingMessageQueue = new Queue<ConnectionMessage>();\n this.privDisconnectDeferral = new Deferred<void>();\n this.privSendMessageQueue = new Queue<ISendItem>();\n this.processSendQueue().catch((reason: string): void => {\n Events.instance.onEvent(new BackgroundEvent(reason));\n });\n } catch (error) {\n this.privConnectionEstablishDeferral.resolve(new ConnectionOpenResponse(500, error as string));\n return this.privConnectionEstablishDeferral.promise;\n }\n\n this.onEvent(new ConnectionStartEvent(this.privConnectionId, this.privUri));\n\n this.privWebsocketClient.onopen = (): void => {\n this.privCertificateValidatedDeferral.promise.then((): void => {\n this.privConnectionState = ConnectionState.Connected;\n this.onEvent(new ConnectionEstablishedEvent(this.privConnectionId));\n this.privConnectionEstablishDeferral.resolve(new ConnectionOpenResponse(200, \"\"));\n }, (error: string): void => {\n this.privConnectionEstablishDeferral.reject(error);\n });\n };\n\n this.privWebsocketClient.onerror = (e: any): void => {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument\n this.onEvent(new ConnectionErrorEvent(this.privConnectionId, e.message, e.type));\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment\n this.privLastErrorReceived = e.message;\n };\n\n this.privWebsocketClient.onclose = (e: { wasClean: boolean; code: number; reason: string; target: any }): void => {\n if (this.privConnectionState === ConnectionState.Connecting) {\n this.privConnectionState = ConnectionState.Disconnected;\n // this.onEvent(new ConnectionEstablishErrorEvent(this.connectionId, e.code, e.reason));\n this.privConnectionEstablishDeferral.resolve(new ConnectionOpenResponse(e.code, e.reason + \" \" + this.privLastErrorReceived));\n } else {\n this.privConnectionState = ConnectionState.Disconnected;\n this.privWebsocketClient = null;\n this.onEvent(new ConnectionClosedEvent(this.privConnectionId, e.code, e.reason));\n }\n\n this.onClose(e.code, e.reason).catch((reason: string): void => {\n Events.instance.onEvent(new BackgroundEvent(reason));\n });\n };\n\n this.privWebsocketClient.onmessage = (e: { data: any; type: string; target: any }): void => {\n const networkReceivedTime = new Date().toISOString();\n if (this.privConnectionState === ConnectionState.Connected) {\n const deferred = new Deferred<ConnectionMessage>();\n // let id = ++this.idCounter;\n this.privReceivingMessageQueue.enqueueFromPromise(deferred.promise);\n if (e.data instanceof ArrayBuffer) {\n const rawMessage = new RawWebsocketMessage(MessageType.Binary, e.data);\n this.privMessageFormatter\n .toConnectionMessage(rawMessage)\n .then((connectionMessage: ConnectionMessage): void => {\n this.onEvent(new ConnectionMessageReceivedEvent(this.privConnectionId, networkReceivedTime, connectionMessage));\n deferred.resolve(connectionMessage);\n }, (error: string): void => {\n // TODO: Events for these ?\n deferred.reject(`Invalid binary message format. Error: ${error}`);\n });\n } else {\n const rawMessage = new RawWebsocketMessage(MessageType.Text, e.data);\n this.privMessageFormatter\n .toConnectionMessage(rawMessage)\n .then((connectionMessage: ConnectionMessage): void => {\n this.onEvent(new ConnectionMessageReceivedEvent(this.privConnectionId, networkReceivedTime, connectionMessage));\n deferred.resolve(connectionMessage);\n }, (error: string): void => {\n // TODO: Events for these ?\n deferred.reject(`Invalid text message format. Error: ${error}`);\n });\n }\n }\n };\n\n return this.privConnectionEstablishDeferral.promise;\n }\n\n public send(message: ConnectionMessage): Promise<void> {\n if (this.privConnectionState !== ConnectionState.Connected) {\n return Promise.reject(`Cannot send on connection that is in ${ConnectionState[this.privConnectionState]} state`);\n }\n\n const messageSendStatusDeferral = new Deferred<void>();\n const messageSendDeferral = new Deferred<ISendItem>();\n\n this.privSendMessageQueue.enqueueFromPromise(messageSendDeferral.promise);\n\n this.privMessageFormatter\n .fromConnectionMessage(message)\n .then((rawMessage: RawWebsocketMessage): void => {\n messageSendDeferral.resolve({\n Message: message,\n RawWebsocketMessage: rawMessage,\n sendStatusDeferral: messageSendStatusDeferral,\n });\n }, (error: string): void => {\n messageSendDeferral.reject(`Error formatting the message. ${error}`);\n });\n\n return messageSendStatusDeferral.promise;\n }\n\n public read(): Promise<ConnectionMessage> {\n if (this.privConnectionState !== ConnectionState.Connected) {\n return Promise.reject<ConnectionMessage>(`Cannot read on connection that is in ${this.privConnectionState} state`);\n }\n\n return this.privReceivingMessageQueue.dequeue();\n }\n\n public close(reason?: string): Promise<void> {\n if (this.privWebsocketClient) {\n if (this.privConnectionState !== ConnectionState.Disconnected) {\n this.privWebsocketClient.close(1000, reason ? reason : \"Normal closure by client\");\n }\n } else {\n return Promise.resolve();\n }\n\n return this.privDisconnectDeferral.promise;\n }\n\n public get events(): EventSource<ConnectionEvent> {\n return this.privConnectionEvents;\n }\n\n private sendRawMessage(sendItem: ISendItem): Promise<void> {\n try {\n // indicates we are draining the queue and it came with no message;\n if (!sendItem) {\n return Promise.resolve();\n }\n\n this.onEvent(new ConnectionMessageSentEvent(this.privConnectionId, new Date().toISOString(), sendItem.Message));\n\n // add a check for the ws readystate in order to stop the red console error 'WebSocket is already in CLOSING or CLOSED state' appearing\n if (this.isWebsocketOpen) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n this.privWebsocketClient.send(sendItem.RawWebsocketMessage.payload);\n } else {\n return Promise.reject(\"websocket send error: Websocket not ready \" + this.privConnectionId + \" \" + sendItem.Message.id + \" \" + new Error().stack);\n }\n return Promise.resolve();\n\n } catch (e) {\n return Promise.reject(`websocket send error: ${e as string}`);\n }\n }\n\n private async onClose(code: number, reason: string): Promise<void> {\n const closeReason = `Connection closed. ${code}: ${reason}`;\n this.privConnectionState = ConnectionState.Disconnected;\n this.privDisconnectDeferral.resolve();\n await this.privReceivingMessageQueue.drainAndDispose((): void => {\n // TODO: Events for these ?\n // Logger.instance.onEvent(new LoggingEvent(LogType.Warning, null, `Failed to process received message. Reason: ${closeReason}, Message: ${JSON.stringify(pendingReceiveItem)}`));\n }, closeReason);\n\n await this.privSendMessageQueue.drainAndDispose((pendingSendItem: ISendItem): void => {\n pendingSendItem.sendStatusDeferral.reject(closeReason);\n }, closeReason);\n }\n\n private async processSendQueue(): Promise<void> {\n while (true) {\n const itemToSend: Promise<ISendItem> = this.privSendMessageQueue.dequeue();\n const sendItem: ISendItem = await itemToSend;\n // indicates we are draining the queue and it came with no message;\n if (!sendItem) {\n return;\n }\n\n try {\n await this.sendRawMessage(sendItem);\n sendItem.sendStatusDeferral.resolve();\n } catch (sendError) {\n sendItem.sendStatusDeferral.reject(sendError as string);\n }\n }\n }\n\n private onEvent(event: ConnectionEvent): void {\n this.privConnectionEvents.onEvent(event);\n Events.instance.onEvent(event);\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n // private getAgent(): http.Agent {\n // // eslint-disable-next-line @typescript-eslint/unbound-method\n // const agent: { proxyInfo: ProxyInfo } = new Agent.Agent(this.createConnection) as unknown as { proxyInfo: ProxyInfo };\n\n // // if (this.proxyInfo !== undefined &&\n // // this.proxyInfo.HostName !== undefined &&\n // // this.proxyInfo.Port > 0) {\n // // agent.proxyInfo = this.proxyInfo;\n // // }\n\n // return agent as unknown as http.Agent;\n // }\n\n // private createConnection(request: Agent.ClientRequest, options: Agent.RequestOptions): Promise<net.Socket> {\n // let socketPromise: Promise<net.Socket>;\n\n // options = {\n // ...options,\n // ...{\n // requestOCSP: true,\n // servername: options.host\n // }\n // };\n\n // if (!!options.secureEndpoint) {\n // socketPromise = Promise.resolve(tls.connect(options));\n // } else {\n // socketPromise = Promise.resolve(net.connect(options));\n // }\n\n // return socketPromise;\n // }\n\n private get isWebsocketOpen(): boolean {\n return this.privWebsocketClient && this.privWebsocketClient.readyState === this.privWebsocketClient.OPEN;\n }\n\n}\n"]}