UNPKG

@euirim/microsoft-cognitiveservices-speech-sdk

Version:
1 lines 16.7 kB
{"version":3,"sources":["src/common.browser/WebsocketMessageAdapter.ts"],"names":[],"mappings":"AAGA,OAAO,EAIH,eAAe,EACf,iBAAiB,EAGjB,sBAAsB,EAEtB,eAAe,EAGf,WAAW,EACX,0BAA0B,EAE1B,OAAO,EAIV,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAexC,qBAAa,uBAAuB;IAChC,OAAO,CAAC,mBAAmB,CAAkB;IAC7C,OAAO,CAAC,oBAAoB,CAA6B;IACzD,OAAO,CAAC,mBAAmB,CAAiB;IAE5C,OAAO,CAAC,oBAAoB,CAAmB;IAC/C,OAAO,CAAC,yBAAyB,CAA2B;IAC5D,OAAO,CAAC,+BAA+B,CAAmC;IAC1E,OAAO,CAAC,gCAAgC,CAAoB;IAC5D,OAAO,CAAC,sBAAsB,CAAoB;IAClD,OAAO,CAAC,oBAAoB,CAA+B;IAC3D,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,WAAW,CAA6B;IAEhD,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,CAAC;KAAE;aAmB5B,KAAK,EAAI,eAAe;IAI5B,IAAI,wCAsJV;IAEM,IAAI,mDAuBV;IAEM,IAAI,mCAMV;IAEM,KAAK,wCAYX;aAEU,MAAM,EAAI,WAAW,CAAC,eAAe,CAAC;IAIjD,OAAO,CAAC,cAAc,CAarB;IAED,OAAO,CAAC,OAAO,CAad;IAED,OAAO,CAAC,gBAAgB,CAoBvB;IAED,OAAO,CAAC,OAAO,CAGd;CACJ","file":"WebsocketMessageAdapter.d.ts","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved.\n// Licensed under the MIT license.\n\nimport {\n ArgumentNullError,\n ConnectionClosedEvent,\n ConnectionEstablishedEvent,\n ConnectionEvent,\n ConnectionMessage,\n ConnectionMessageReceivedEvent,\n ConnectionMessageSentEvent,\n ConnectionOpenResponse,\n ConnectionStartEvent,\n ConnectionState,\n Deferred,\n Events,\n EventSource,\n IWebsocketMessageFormatter,\n MessageType,\n Promise,\n PromiseHelper,\n Queue,\n RawWebsocketMessage,\n} from \"../common/Exports\";\nimport { ProxyInfo } from \"./ProxyInfo\";\n\n// Node.JS specific web socket / browser support.\nimport * as http from \"http\";\nimport * as HttpsProxyAgent from \"https-proxy-agent\";\nimport * as tls from \"tls\";\nimport * as ws from \"ws\";\nimport * as ocsp from \"../../external/ocsp/ocsp\";\n\ninterface ISendItem {\n Message: ConnectionMessage;\n RawWebsocketMessage: RawWebsocketMessage;\n sendStatusDeferral: Deferred<boolean>;\n}\n\nexport class WebsocketMessageAdapter {\n private privConnectionState: ConnectionState;\n private privMessageFormatter: IWebsocketMessageFormatter;\n private privWebsocketClient: WebSocket | ws;\n\n private privSendMessageQueue: Queue<ISendItem>;\n private privReceivingMessageQueue: Queue<ConnectionMessage>;\n private privConnectionEstablishDeferral: Deferred<ConnectionOpenResponse>;\n private privCertificateValidatedDeferral: Deferred<boolean>;\n private privDisconnectDeferral: Deferred<boolean>;\n private privConnectionEvents: EventSource<ConnectionEvent>;\n private privConnectionId: string;\n private privUri: string;\n private proxyInfo: ProxyInfo;\n private privHeaders: { [key: string]: string; };\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\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 }\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 PromiseHelper.fromError<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<boolean>();\n\n this.privConnectionState = ConnectionState.Connecting;\n\n try {\n if (typeof WebSocket !== \"undefined\" && !WebsocketMessageAdapter.forceNpmWebSocket) {\n // Browser handles cert checks.\n this.privCertificateValidatedDeferral.resolve(true);\n\n this.privWebsocketClient = new WebSocket(this.privUri);\n } else {\n if (this.proxyInfo !== undefined &&\n this.proxyInfo.HostName !== undefined &&\n this.proxyInfo.Port > 0) {\n const httpProxyOptions: HttpsProxyAgent.HttpsProxyAgentOptions = {\n host: this.proxyInfo.HostName,\n port: this.proxyInfo.Port,\n };\n\n if (undefined !== this.proxyInfo.UserName) {\n httpProxyOptions.headers = {\n \"Proxy-Authentication\": \"Basic \" + new Buffer(this.proxyInfo.UserName + \":\" + (this.proxyInfo.Password === undefined) ? \"\" : this.proxyInfo.Password).toString(\"base64\"),\n \"requestOCSP\": \"true\",\n };\n }\n\n const httpProxyAgent: HttpsProxyAgent = new HttpsProxyAgent(httpProxyOptions);\n const httpsOptions: http.RequestOptions = { agent: httpProxyAgent , headers: this.privHeaders };\n\n this.privWebsocketClient = new ws(this.privUri, httpsOptions as ws.ClientOptions);\n\n // Register to be notified when WebSocket upgrade happens so we can check the validity of the\n // Certificate.\n this.privWebsocketClient.addListener(\"upgrade\", (e: http.IncomingMessage): void => {\n const tlsSocket: tls.TLSSocket = e.socket as tls.TLSSocket;\n const peer: tls.DetailedPeerCertificate = tlsSocket.getPeerCertificate(true);\n\n // Cork the socket until we know if the cert is good.\n tlsSocket.cork();\n\n ocsp.check({\n cert: peer.raw,\n httpOptions: httpsOptions,\n issuer: peer.issuerCertificate.raw,\n }, (error: Error, res: any): void => {\n if (error) {\n this.privCertificateValidatedDeferral.reject(error.message);\n tlsSocket.destroy(error);\n } else {\n this.privCertificateValidatedDeferral.resolve(true);\n tlsSocket.uncork();\n }\n });\n });\n\n } else {\n // The ocsp library will handle validation for us and fail the connection if needed.\n this.privCertificateValidatedDeferral.resolve(true);\n\n const ocspAgent: ocsp.Agent = new ocsp.Agent({});\n const options: ws.ClientOptions = { agent: ocspAgent, headers: this.privHeaders };\n this.privWebsocketClient = new ws(this.privUri, options);\n }\n }\n\n this.privWebsocketClient.binaryType = \"arraybuffer\";\n this.privReceivingMessageQueue = new Queue<ConnectionMessage>();\n this.privDisconnectDeferral = new Deferred<boolean>();\n this.privSendMessageQueue = new Queue<ISendItem>();\n this.processSendQueue();\n } catch (error) {\n this.privConnectionEstablishDeferral.resolve(new ConnectionOpenResponse(500, error));\n return this.privConnectionEstablishDeferral.promise();\n }\n\n this.onEvent(new ConnectionStartEvent(this.privConnectionId, this.privUri));\n\n this.privWebsocketClient.onopen = (e: { target: WebSocket | ws }) => {\n this.privCertificateValidatedDeferral.promise().on((): 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: { error: any; message: string; type: string; target: WebSocket | ws }) => {\n // TODO: Understand what this is error is. Will we still get onClose ?\n if (this.privConnectionState !== ConnectionState.Connecting) {\n // TODO: Is this required ?\n // this.onEvent(new ConnectionErrorEvent(errorMsg, connectionId));\n }\n };\n\n this.privWebsocketClient.onclose = (e: { wasClean: boolean; code: number; reason: string; target: WebSocket | ws }) => {\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));\n } else {\n this.onEvent(new ConnectionClosedEvent(this.privConnectionId, e.code, e.reason));\n }\n\n this.onClose(e.code, e.reason);\n };\n\n this.privWebsocketClient.onmessage = (e: { data: ws.Data; type: string; target: WebSocket | ws }) => {\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 .on((connectionMessage: ConnectionMessage) => {\n this.onEvent(new ConnectionMessageReceivedEvent(this.privConnectionId, networkReceivedTime, connectionMessage));\n deferred.resolve(connectionMessage);\n }, (error: string) => {\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 .on((connectionMessage: ConnectionMessage) => {\n this.onEvent(new ConnectionMessageReceivedEvent(this.privConnectionId, networkReceivedTime, connectionMessage));\n deferred.resolve(connectionMessage);\n }, (error: string) => {\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<boolean> => {\n if (this.privConnectionState !== ConnectionState.Connected) {\n return PromiseHelper.fromError<boolean>(`Cannot send on connection that is in ${this.privConnectionState} state`);\n }\n\n const messageSendStatusDeferral = new Deferred<boolean>();\n const messageSendDeferral = new Deferred<ISendItem>();\n\n this.privSendMessageQueue.enqueueFromPromise(messageSendDeferral.promise());\n\n this.privMessageFormatter\n .fromConnectionMessage(message)\n .on((rawMessage: RawWebsocketMessage) => {\n messageSendDeferral.resolve({\n Message: message,\n RawWebsocketMessage: rawMessage,\n sendStatusDeferral: messageSendStatusDeferral,\n });\n }, (error: string) => {\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 PromiseHelper.fromError<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<boolean> => {\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 const deferral = new Deferred<boolean>();\n deferral.resolve(true);\n return deferral.promise();\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<boolean> => {\n try {\n // indicates we are draining the queue and it came with no message;\n if (!sendItem) {\n return PromiseHelper.fromResult(true);\n }\n\n this.onEvent(new ConnectionMessageSentEvent(this.privConnectionId, new Date().toISOString(), sendItem.Message));\n this.privWebsocketClient.send(sendItem.RawWebsocketMessage.payload);\n return PromiseHelper.fromResult(true);\n } catch (e) {\n return PromiseHelper.fromError<boolean>(`websocket send error: ${e}`);\n }\n }\n\n private onClose = (code: number, reason: string): void => {\n const closeReason = `Connection closed. ${code}: ${reason}`;\n this.privConnectionState = ConnectionState.Disconnected;\n this.privDisconnectDeferral.resolve(true);\n this.privReceivingMessageQueue.dispose(reason);\n this.privReceivingMessageQueue.drainAndDispose((pendingReceiveItem: ConnectionMessage) => {\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 this.privSendMessageQueue.drainAndDispose((pendingSendItem: ISendItem) => {\n pendingSendItem.sendStatusDeferral.reject(closeReason);\n }, closeReason);\n }\n\n private processSendQueue = (): void => {\n this.privSendMessageQueue\n .dequeue()\n .on((sendItem: ISendItem) => {\n // indicates we are draining the queue and it came with no message;\n if (!sendItem) {\n return;\n }\n\n this.sendRawMessage(sendItem)\n .on((result: boolean) => {\n sendItem.sendStatusDeferral.resolve(result);\n this.processSendQueue();\n }, (sendError: string) => {\n sendItem.sendStatusDeferral.reject(sendError);\n this.processSendQueue();\n });\n }, (error: string) => {\n // do nothing\n });\n }\n\n private onEvent = (event: ConnectionEvent): void => {\n this.privConnectionEvents.onEvent(event);\n Events.instance.onEvent(event);\n }\n}\n"]}