UNPKG

@euirim/microsoft-cognitiveservices-speech-sdk

Version:
259 lines (257 loc) 13.7 kB
// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. import { ArgumentNullError, ConnectionClosedEvent, ConnectionEstablishedEvent, ConnectionMessageReceivedEvent, ConnectionMessageSentEvent, ConnectionOpenResponse, ConnectionStartEvent, ConnectionState, Deferred, Events, EventSource, MessageType, PromiseHelper, Queue, RawWebsocketMessage, } from "../common/Exports"; import * as HttpsProxyAgent from "https-proxy-agent"; import * as ws from "ws"; import * as ocsp from "../../external/ocsp/ocsp"; export class WebsocketMessageAdapter { constructor(uri, connectionId, messageFormatter, proxyInfo, headers) { this.open = () => { if (this.privConnectionState === ConnectionState.Disconnected) { return PromiseHelper.fromError(`Cannot open a connection that is in ${this.privConnectionState} state`); } if (this.privConnectionEstablishDeferral) { return this.privConnectionEstablishDeferral.promise(); } this.privConnectionEstablishDeferral = new Deferred(); this.privCertificateValidatedDeferral = new Deferred(); this.privConnectionState = ConnectionState.Connecting; try { if (typeof WebSocket !== "undefined" && !WebsocketMessageAdapter.forceNpmWebSocket) { // Browser handles cert checks. this.privCertificateValidatedDeferral.resolve(true); this.privWebsocketClient = new WebSocket(this.privUri); } else { if (this.proxyInfo !== undefined && this.proxyInfo.HostName !== undefined && this.proxyInfo.Port > 0) { const httpProxyOptions = { host: this.proxyInfo.HostName, port: this.proxyInfo.Port, }; if (undefined !== this.proxyInfo.UserName) { httpProxyOptions.headers = { "Proxy-Authentication": "Basic " + new Buffer(this.proxyInfo.UserName + ":" + (this.proxyInfo.Password === undefined) ? "" : this.proxyInfo.Password).toString("base64"), "requestOCSP": "true", }; } const httpProxyAgent = new HttpsProxyAgent(httpProxyOptions); const httpsOptions = { agent: httpProxyAgent, headers: this.privHeaders }; this.privWebsocketClient = new ws(this.privUri, httpsOptions); // Register to be notified when WebSocket upgrade happens so we can check the validity of the // Certificate. this.privWebsocketClient.addListener("upgrade", (e) => { const tlsSocket = e.socket; const peer = tlsSocket.getPeerCertificate(true); // Cork the socket until we know if the cert is good. tlsSocket.cork(); ocsp.check({ cert: peer.raw, httpOptions: httpsOptions, issuer: peer.issuerCertificate.raw, }, (error, res) => { if (error) { this.privCertificateValidatedDeferral.reject(error.message); tlsSocket.destroy(error); } else { this.privCertificateValidatedDeferral.resolve(true); tlsSocket.uncork(); } }); }); } else { // The ocsp library will handle validation for us and fail the connection if needed. this.privCertificateValidatedDeferral.resolve(true); const ocspAgent = new ocsp.Agent({}); const options = { agent: ocspAgent, headers: this.privHeaders }; this.privWebsocketClient = new ws(this.privUri, options); } } this.privWebsocketClient.binaryType = "arraybuffer"; this.privReceivingMessageQueue = new Queue(); this.privDisconnectDeferral = new Deferred(); this.privSendMessageQueue = new Queue(); this.processSendQueue(); } catch (error) { this.privConnectionEstablishDeferral.resolve(new ConnectionOpenResponse(500, error)); return this.privConnectionEstablishDeferral.promise(); } this.onEvent(new ConnectionStartEvent(this.privConnectionId, this.privUri)); this.privWebsocketClient.onopen = (e) => { this.privCertificateValidatedDeferral.promise().on(() => { this.privConnectionState = ConnectionState.Connected; this.onEvent(new ConnectionEstablishedEvent(this.privConnectionId)); this.privConnectionEstablishDeferral.resolve(new ConnectionOpenResponse(200, "")); }, (error) => { this.privConnectionEstablishDeferral.reject(error); }); }; this.privWebsocketClient.onerror = (e) => { // TODO: Understand what this is error is. Will we still get onClose ? if (this.privConnectionState !== ConnectionState.Connecting) { // TODO: Is this required ? // this.onEvent(new ConnectionErrorEvent(errorMsg, connectionId)); } }; this.privWebsocketClient.onclose = (e) => { if (this.privConnectionState === ConnectionState.Connecting) { this.privConnectionState = ConnectionState.Disconnected; // this.onEvent(new ConnectionEstablishErrorEvent(this.connectionId, e.code, e.reason)); this.privConnectionEstablishDeferral.resolve(new ConnectionOpenResponse(e.code, e.reason)); } else { this.onEvent(new ConnectionClosedEvent(this.privConnectionId, e.code, e.reason)); } this.onClose(e.code, e.reason); }; this.privWebsocketClient.onmessage = (e) => { const networkReceivedTime = new Date().toISOString(); if (this.privConnectionState === ConnectionState.Connected) { const deferred = new Deferred(); // let id = ++this.idCounter; this.privReceivingMessageQueue.enqueueFromPromise(deferred.promise()); if (e.data instanceof ArrayBuffer) { const rawMessage = new RawWebsocketMessage(MessageType.Binary, e.data); this.privMessageFormatter .toConnectionMessage(rawMessage) .on((connectionMessage) => { this.onEvent(new ConnectionMessageReceivedEvent(this.privConnectionId, networkReceivedTime, connectionMessage)); deferred.resolve(connectionMessage); }, (error) => { // TODO: Events for these ? deferred.reject(`Invalid binary message format. Error: ${error}`); }); } else { const rawMessage = new RawWebsocketMessage(MessageType.Text, e.data); this.privMessageFormatter .toConnectionMessage(rawMessage) .on((connectionMessage) => { this.onEvent(new ConnectionMessageReceivedEvent(this.privConnectionId, networkReceivedTime, connectionMessage)); deferred.resolve(connectionMessage); }, (error) => { // TODO: Events for these ? deferred.reject(`Invalid text message format. Error: ${error}`); }); } } }; return this.privConnectionEstablishDeferral.promise(); }; this.send = (message) => { if (this.privConnectionState !== ConnectionState.Connected) { return PromiseHelper.fromError(`Cannot send on connection that is in ${this.privConnectionState} state`); } const messageSendStatusDeferral = new Deferred(); const messageSendDeferral = new Deferred(); this.privSendMessageQueue.enqueueFromPromise(messageSendDeferral.promise()); this.privMessageFormatter .fromConnectionMessage(message) .on((rawMessage) => { messageSendDeferral.resolve({ Message: message, RawWebsocketMessage: rawMessage, sendStatusDeferral: messageSendStatusDeferral, }); }, (error) => { messageSendDeferral.reject(`Error formatting the message. ${error}`); }); return messageSendStatusDeferral.promise(); }; this.read = () => { if (this.privConnectionState !== ConnectionState.Connected) { return PromiseHelper.fromError(`Cannot read on connection that is in ${this.privConnectionState} state`); } return this.privReceivingMessageQueue.dequeue(); }; this.close = (reason) => { if (this.privWebsocketClient) { if (this.privConnectionState !== ConnectionState.Disconnected) { this.privWebsocketClient.close(1000, reason ? reason : "Normal closure by client"); } } else { const deferral = new Deferred(); deferral.resolve(true); return deferral.promise(); } return this.privDisconnectDeferral.promise(); }; this.sendRawMessage = (sendItem) => { try { // indicates we are draining the queue and it came with no message; if (!sendItem) { return PromiseHelper.fromResult(true); } this.onEvent(new ConnectionMessageSentEvent(this.privConnectionId, new Date().toISOString(), sendItem.Message)); this.privWebsocketClient.send(sendItem.RawWebsocketMessage.payload); return PromiseHelper.fromResult(true); } catch (e) { return PromiseHelper.fromError(`websocket send error: ${e}`); } }; this.onClose = (code, reason) => { const closeReason = `Connection closed. ${code}: ${reason}`; this.privConnectionState = ConnectionState.Disconnected; this.privDisconnectDeferral.resolve(true); this.privReceivingMessageQueue.dispose(reason); this.privReceivingMessageQueue.drainAndDispose((pendingReceiveItem) => { // TODO: Events for these ? // Logger.instance.onEvent(new LoggingEvent(LogType.Warning, null, `Failed to process received message. Reason: ${closeReason}, Message: ${JSON.stringify(pendingReceiveItem)}`)); }, closeReason); this.privSendMessageQueue.drainAndDispose((pendingSendItem) => { pendingSendItem.sendStatusDeferral.reject(closeReason); }, closeReason); }; this.processSendQueue = () => { this.privSendMessageQueue .dequeue() .on((sendItem) => { // indicates we are draining the queue and it came with no message; if (!sendItem) { return; } this.sendRawMessage(sendItem) .on((result) => { sendItem.sendStatusDeferral.resolve(result); this.processSendQueue(); }, (sendError) => { sendItem.sendStatusDeferral.reject(sendError); this.processSendQueue(); }); }, (error) => { // do nothing }); }; this.onEvent = (event) => { this.privConnectionEvents.onEvent(event); Events.instance.onEvent(event); }; if (!uri) { throw new ArgumentNullError("uri"); } if (!messageFormatter) { throw new ArgumentNullError("messageFormatter"); } this.proxyInfo = proxyInfo; this.privConnectionEvents = new EventSource(); this.privConnectionId = connectionId; this.privMessageFormatter = messageFormatter; this.privConnectionState = ConnectionState.None; this.privUri = uri; this.privHeaders = headers; } get state() { return this.privConnectionState; } get events() { return this.privConnectionEvents; } } WebsocketMessageAdapter.forceNpmWebSocket = false; //# sourceMappingURL=WebsocketMessageAdapter.js.map