@euirim/microsoft-cognitiveservices-speech-sdk
Version:
Microsoft Cognitive Services Speech SDK for JavaScript
259 lines (257 loc) • 13.7 kB
JavaScript
// 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