microsoft-cognitiveservices-speech-sdk
Version:
Microsoft Cognitive Services Speech SDK for JavaScript
331 lines (329 loc) • 15.8 kB
JavaScript
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.WebsocketMessageAdapter = void 0;
const net = __importStar(require("net"));
const tls = __importStar(require("tls"));
const agent_base_1 = __importDefault(require("agent-base"));
const https_proxy_agent_1 = __importDefault(require("https-proxy-agent"));
const ws_1 = __importDefault(require("ws"));
const HeaderNames_js_1 = require("../common.speech/HeaderNames.js");
const Exports_js_1 = require("../common/Exports.js");
class WebsocketMessageAdapter {
constructor(uri, connectionId, messageFormatter, proxyInfo, headers, enableCompression) {
if (!uri) {
throw new Exports_js_1.ArgumentNullError("uri");
}
if (!messageFormatter) {
throw new Exports_js_1.ArgumentNullError("messageFormatter");
}
this.proxyInfo = proxyInfo;
this.privConnectionEvents = new Exports_js_1.EventSource();
this.privConnectionId = connectionId;
this.privMessageFormatter = messageFormatter;
this.privConnectionState = Exports_js_1.ConnectionState.None;
this.privUri = uri;
this.privHeaders = headers;
this.privEnableCompression = enableCompression;
// Add the connection ID to the headers
this.privHeaders[HeaderNames_js_1.HeaderNames.ConnectionId] = this.privConnectionId;
this.privLastErrorReceived = "";
}
get state() {
return this.privConnectionState;
}
open() {
if (this.privConnectionState === Exports_js_1.ConnectionState.Disconnected) {
return Promise.reject(`Cannot open a connection that is in ${this.privConnectionState} state`);
}
if (this.privConnectionEstablishDeferral) {
return this.privConnectionEstablishDeferral.promise;
}
this.privConnectionEstablishDeferral = new Exports_js_1.Deferred();
this.privCertificateValidatedDeferral = new Exports_js_1.Deferred();
this.privConnectionState = Exports_js_1.ConnectionState.Connecting;
try {
if (typeof WebSocket !== "undefined" && !WebsocketMessageAdapter.forceNpmWebSocket) {
// Browser handles cert checks.
this.privCertificateValidatedDeferral.resolve();
this.privWebsocketClient = new WebSocket(this.privUri);
}
else {
const options = { headers: this.privHeaders, perMessageDeflate: this.privEnableCompression };
// The ocsp library will handle validation for us and fail the connection if needed.
this.privCertificateValidatedDeferral.resolve();
options.agent = this.getAgent();
// Workaround for https://github.com/microsoft/cognitive-services-speech-sdk-js/issues/465
// Which is root caused by https://github.com/TooTallNate/node-agent-base/issues/61
const uri = new URL(this.privUri);
let protocol = uri.protocol;
if (protocol?.toLocaleLowerCase() === "wss:") {
protocol = "https:";
}
else if (protocol?.toLocaleLowerCase() === "ws:") {
protocol = "http:";
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
options.agent.protocol = protocol;
this.privWebsocketClient = new ws_1.default(this.privUri, options);
}
this.privWebsocketClient.binaryType = "arraybuffer";
this.privReceivingMessageQueue = new Exports_js_1.Queue();
this.privDisconnectDeferral = new Exports_js_1.Deferred();
this.privSendMessageQueue = new Exports_js_1.Queue();
this.processSendQueue().catch((reason) => {
Exports_js_1.Events.instance.onEvent(new Exports_js_1.BackgroundEvent(reason));
});
}
catch (error) {
this.privConnectionEstablishDeferral.resolve(new Exports_js_1.ConnectionOpenResponse(500, error));
return this.privConnectionEstablishDeferral.promise;
}
this.onEvent(new Exports_js_1.ConnectionStartEvent(this.privConnectionId, this.privUri));
this.privWebsocketClient.onopen = () => {
this.privCertificateValidatedDeferral.promise.then(() => {
this.privConnectionState = Exports_js_1.ConnectionState.Connected;
this.onEvent(new Exports_js_1.ConnectionEstablishedEvent(this.privConnectionId));
this.privConnectionEstablishDeferral.resolve(new Exports_js_1.ConnectionOpenResponse(200, ""));
}, (error) => {
this.privConnectionEstablishDeferral.reject(error);
});
};
this.privWebsocketClient.onerror = (e) => {
this.onEvent(new Exports_js_1.ConnectionErrorEvent(this.privConnectionId, e.message, e.type));
this.privLastErrorReceived = e.message;
};
this.privWebsocketClient.onclose = (e) => {
if (this.privConnectionState === Exports_js_1.ConnectionState.Connecting) {
this.privConnectionState = Exports_js_1.ConnectionState.Disconnected;
// this.onEvent(new ConnectionEstablishErrorEvent(this.connectionId, e.code, e.reason));
this.privConnectionEstablishDeferral.resolve(new Exports_js_1.ConnectionOpenResponse(e.code, e.reason + " " + this.privLastErrorReceived));
}
else {
this.privConnectionState = Exports_js_1.ConnectionState.Disconnected;
this.privWebsocketClient = null;
this.onEvent(new Exports_js_1.ConnectionClosedEvent(this.privConnectionId, e.code, e.reason));
}
this.onClose(e.code, e.reason).catch((reason) => {
Exports_js_1.Events.instance.onEvent(new Exports_js_1.BackgroundEvent(reason));
});
};
this.privWebsocketClient.onmessage = (e) => {
const networkReceivedTime = new Date().toISOString();
if (this.privConnectionState === Exports_js_1.ConnectionState.Connected) {
const deferred = new Exports_js_1.Deferred();
// let id = ++this.idCounter;
this.privReceivingMessageQueue.enqueueFromPromise(deferred.promise);
if (e.data instanceof ArrayBuffer) {
const rawMessage = new Exports_js_1.RawWebsocketMessage(Exports_js_1.MessageType.Binary, e.data);
this.privMessageFormatter
.toConnectionMessage(rawMessage)
.then((connectionMessage) => {
this.onEvent(new Exports_js_1.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 Exports_js_1.RawWebsocketMessage(Exports_js_1.MessageType.Text, e.data);
this.privMessageFormatter
.toConnectionMessage(rawMessage)
.then((connectionMessage) => {
this.onEvent(new Exports_js_1.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;
}
send(message) {
if (this.privConnectionState !== Exports_js_1.ConnectionState.Connected) {
return Promise.reject(`Cannot send on connection that is in ${Exports_js_1.ConnectionState[this.privConnectionState]} state`);
}
const messageSendStatusDeferral = new Exports_js_1.Deferred();
const messageSendDeferral = new Exports_js_1.Deferred();
this.privSendMessageQueue.enqueueFromPromise(messageSendDeferral.promise);
this.privMessageFormatter
.fromConnectionMessage(message)
.then((rawMessage) => {
messageSendDeferral.resolve({
Message: message,
RawWebsocketMessage: rawMessage,
sendStatusDeferral: messageSendStatusDeferral,
});
}, (error) => {
messageSendDeferral.reject(`Error formatting the message. ${error}`);
});
return messageSendStatusDeferral.promise;
}
read() {
if (this.privConnectionState !== Exports_js_1.ConnectionState.Connected) {
return Promise.reject(`Cannot read on connection that is in ${this.privConnectionState} state`);
}
return this.privReceivingMessageQueue.dequeue();
}
close(reason) {
if (this.privWebsocketClient) {
if (this.privConnectionState !== Exports_js_1.ConnectionState.Disconnected) {
this.privWebsocketClient.close(1000, reason ? reason : "Normal closure by client");
}
}
else {
return Promise.resolve();
}
return this.privDisconnectDeferral.promise;
}
get events() {
return this.privConnectionEvents;
}
sendRawMessage(sendItem) {
try {
// indicates we are draining the queue and it came with no message;
if (!sendItem) {
return Promise.resolve();
}
this.onEvent(new Exports_js_1.ConnectionMessageSentEvent(this.privConnectionId, new Date().toISOString(), sendItem.Message));
// add a check for the ws readystate in order to stop the red console error 'WebSocket is already in CLOSING or CLOSED state' appearing
if (this.isWebsocketOpen) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
this.privWebsocketClient.send(sendItem.RawWebsocketMessage.payload);
}
else {
return Promise.reject("websocket send error: Websocket not ready " + this.privConnectionId + " " + sendItem.Message.id + " " + new Error().stack);
}
return Promise.resolve();
}
catch (e) {
return Promise.reject(`websocket send error: ${e}`);
}
}
async onClose(code, reason) {
const closeReason = `Connection closed. ${code}: ${reason}`;
this.privConnectionState = Exports_js_1.ConnectionState.Disconnected;
this.privDisconnectDeferral.resolve();
await this.privReceivingMessageQueue.drainAndDispose(() => {
// TODO: Events for these ?
// Logger.instance.onEvent(new LoggingEvent(LogType.Warning, null, `Failed to process received message. Reason: ${closeReason}, Message: ${JSON.stringify(pendingReceiveItem)}`));
}, closeReason);
await this.privSendMessageQueue.drainAndDispose((pendingSendItem) => {
pendingSendItem.sendStatusDeferral.reject(closeReason);
}, closeReason);
}
async processSendQueue() {
while (true) {
const itemToSend = this.privSendMessageQueue.dequeue();
const sendItem = await itemToSend;
// indicates we are draining the queue and it came with no message;
if (!sendItem) {
return;
}
try {
await this.sendRawMessage(sendItem);
sendItem.sendStatusDeferral.resolve();
}
catch (sendError) {
sendItem.sendStatusDeferral.reject(sendError);
}
}
}
onEvent(event) {
this.privConnectionEvents.onEvent(event);
Exports_js_1.Events.instance.onEvent(event);
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
getAgent() {
// eslint-disable-next-line @typescript-eslint/unbound-method
const agent = new agent_base_1.default.Agent(this.createConnection);
if (this.proxyInfo !== undefined &&
this.proxyInfo.HostName !== undefined &&
this.proxyInfo.Port > 0) {
agent.proxyInfo = this.proxyInfo;
}
return agent;
}
static GetProxyAgent(proxyInfo) {
const httpProxyOptions = {
host: proxyInfo.HostName,
port: proxyInfo.Port,
};
if (!!proxyInfo.UserName) {
httpProxyOptions.headers = {
"Proxy-Authentication": "Basic " + new Buffer(`${proxyInfo.UserName}:${(proxyInfo.Password === undefined) ? "" : proxyInfo.Password}`).toString("base64"),
};
}
else {
httpProxyOptions.headers = {};
}
httpProxyOptions.headers.requestOCSP = "true";
const httpProxyAgent = new https_proxy_agent_1.default(httpProxyOptions);
return httpProxyAgent;
}
createConnection(request, options) {
let socketPromise;
options = {
...options,
...{
requestOCSP: true,
servername: options.host
}
};
if (!!this.proxyInfo) {
const httpProxyAgent = WebsocketMessageAdapter.GetProxyAgent(this.proxyInfo);
const baseAgent = httpProxyAgent;
socketPromise = new Promise((resolve, reject) => {
baseAgent.callback(request, options, (error, socket) => {
if (!!error) {
reject(error);
}
else {
resolve(socket);
}
});
});
}
else {
if (!!options.secureEndpoint) {
socketPromise = Promise.resolve(tls.connect(options));
}
else {
socketPromise = Promise.resolve(net.connect(options));
}
}
return socketPromise;
}
get isWebsocketOpen() {
return this.privWebsocketClient && this.privWebsocketClient.readyState === this.privWebsocketClient.OPEN;
}
}
exports.WebsocketMessageAdapter = WebsocketMessageAdapter;
WebsocketMessageAdapter.forceNpmWebSocket = false;
//# sourceMappingURL=WebsocketMessageAdapter.js.map