UNPKG

botframework-streaming

Version:

Streaming library for the Microsoft Bot Framework

175 lines 7.31 kB
"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