175 lines
No EOL
7.1 KiB
JavaScript
175 lines
No EOL
7.1 KiB
JavaScript
"use strict";
|
|
/**
|
|
* @module botframework-streaming
|
|
*/
|
|
/**
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License.
|
|
*/
|
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
});
|
|
};
|
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
};
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.NodeWebSocket = void 0;
|
|
const http_1 = require("http");
|
|
const url_1 = require("url");
|
|
const crypto_1 = __importDefault(require("crypto"));
|
|
const ws_1 = __importDefault(require("ws"));
|
|
const NONCE_LENGTH = 16;
|
|
/**
|
|
* An implementation of [ISocket](xref:botframework-streaming.ISocket) to use with a [NodeWebSocketFactory](xref:botframework-streaming.NodeWebSocketFactory) to create a WebSocket server.
|
|
*/
|
|
class NodeWebSocket {
|
|
/**
|
|
* Creates a new [NodeWebSocket](xref:botframework-streaming.NodeWebSocket) instance.
|
|
*
|
|
* @param wsSocket The `ws` WebSocket instance to build this connection on.
|
|
*/
|
|
constructor(wsSocket) {
|
|
this.wsSocket = wsSocket;
|
|
}
|
|
/**
|
|
* @internal
|
|
*/
|
|
create(req, socket, head) {
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|
this.wsServer = new ws_1.default.Server({ noServer: true });
|
|
return new Promise((resolve, reject) => {
|
|
try {
|
|
this.wsServer.handleUpgrade(req, socket, head, (websocket) => {
|
|
this.wsSocket = websocket;
|
|
resolve();
|
|
});
|
|
}
|
|
catch (err) {
|
|
reject(err);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
/**
|
|
* Indicates if the 'ws' WebSocket is currently connected and ready to send messages.
|
|
*
|
|
* @returns `true` if the underlying websocket is ready and availble to send messages, otherwise `false`.
|
|
*/
|
|
get isConnected() {
|
|
return this.wsSocket && this.wsSocket.readyState === ws_1.default.OPEN;
|
|
}
|
|
/**
|
|
* Writes a buffer to the socket and sends it.
|
|
*
|
|
* @param buffer The buffer of data to send across the connection.
|
|
*/
|
|
write(buffer) {
|
|
this.wsSocket.send(buffer);
|
|
}
|
|
/**
|
|
* Connects to the supporting socket using WebSocket protocol.
|
|
*
|
|
* @param serverAddressOrHostName The host name or URL the server is listening on.
|
|
* @param port If `serverAddressOrHostName` is a host name, the port the server is listening on, defaults to 8082. Otherwise, this argument is ignored.
|
|
* @returns A Promise that resolves when the websocket connection is closed, or rejects on an error.
|
|
*/
|
|
connect(serverAddressOrHostName, port = 8082) {
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|
let url;
|
|
try {
|
|
url = new url_1.URL(serverAddressOrHostName);
|
|
// eslint-disable-next-line no-empty
|
|
}
|
|
catch (_error) { }
|
|
if (url === null || url === void 0 ? void 0 : url.hostname) {
|
|
return new Promise((resolve, reject) => {
|
|
const ws = (this.wsSocket = new ws_1.default(url));
|
|
ws.once('error', ({ message }) => reject(new Error(message)));
|
|
ws.once('open', () => resolve());
|
|
});
|
|
}
|
|
// [hawo]: The following logics are kept here for backward compatibility.
|
|
//
|
|
// However, there are no tests to prove the following code works.
|
|
// We tried our best to write a test and figure out how the code would work.
|
|
//
|
|
// However, there are obvious mistakes in the code that made it very unlikely to work:
|
|
// - `options.headers.upgrade` must set to `'websocket'`
|
|
// - Second argument of `WebSocket.server.completeUpgrade` should be `{}`, instead of `undefined`
|
|
//
|
|
// More readings at https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers#client_handshake_request.
|
|
this.wsServer = new ws_1.default.Server({ noServer: true });
|
|
// Key generation per https://tools.ietf.org/html/rfc6455#section-1.3 (pg. 7)
|
|
const wskey = crypto_1.default.randomBytes(NONCE_LENGTH).toString('base64');
|
|
const options = {
|
|
port: port,
|
|
hostname: serverAddressOrHostName,
|
|
headers: {
|
|
connection: 'upgrade',
|
|
'Sec-WebSocket-Key': wskey,
|
|
'Sec-WebSocket-Version': '13',
|
|
},
|
|
};
|
|
const req = (0, http_1.request)(options);
|
|
req.end();
|
|
req.on('upgrade', (res, socket, head) => {
|
|
// @types/ws does not contain the signature for completeUpgrade
|
|
// https://github.com/websockets/ws/blob/0a612364e69fc07624b8010c6873f7766743a8e3/lib/websocket-server.js#L269
|
|
this.wsServer.completeUpgrade(wskey, undefined, res, socket, head, (websocket) => {
|
|
this.wsSocket = websocket;
|
|
});
|
|
});
|
|
return new Promise((resolve, reject) => {
|
|
req.on('close', resolve);
|
|
req.on('error', reject);
|
|
});
|
|
});
|
|
}
|
|
/**
|
|
* Set the handler for `'message'` events received on the socket.
|
|
*
|
|
* @param handler The callback to handle the "message" event.
|
|
*/
|
|
setOnMessageHandler(handler) {
|
|
this.wsSocket.on('message', handler);
|
|
}
|
|
/**
|
|
* Close the socket.
|
|
*
|
|
* @remarks
|
|
* Optionally pass in a status code and string explaining why the connection is closing.
|
|
* @param code Optional status code to explain why the connection has closed.
|
|
* @param data Optional additional data to explain why the connection has closed.
|
|
*/
|
|
close(code, data) {
|
|
this.wsSocket.close(code, data);
|
|
}
|
|
/**
|
|
* Set the callback to call when encountering socket closures.
|
|
*
|
|
* @param handler The callback to handle the "close" event.
|
|
*/
|
|
setOnCloseHandler(handler) {
|
|
this.wsSocket.on('close', handler);
|
|
}
|
|
/**
|
|
* Set the callback to call when encountering errors.
|
|
*
|
|
* @param handler The callback to handle the "error" event.
|
|
*/
|
|
setOnErrorHandler(handler) {
|
|
this.wsSocket.on('error', (error) => {
|
|
if (error) {
|
|
handler(error);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
exports.NodeWebSocket = NodeWebSocket;
|
|
//# sourceMappingURL=nodeWebSocket.js.map
|