UNPKG

@microsoft/signalr

Version:
159 lines 7.54 kB
"use strict"; // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. Object.defineProperty(exports, "__esModule", { value: true }); exports.WebSocketTransport = void 0; const HeaderNames_1 = require("./HeaderNames"); const ILogger_1 = require("./ILogger"); const ITransport_1 = require("./ITransport"); const Utils_1 = require("./Utils"); /** @private */ class WebSocketTransport { constructor(httpClient, accessTokenFactory, logger, logMessageContent, webSocketConstructor, headers) { this._logger = logger; this._accessTokenFactory = accessTokenFactory; this._logMessageContent = logMessageContent; this._webSocketConstructor = webSocketConstructor; this._httpClient = httpClient; this.onreceive = null; this.onclose = null; this._headers = headers; } async connect(url, transferFormat) { Utils_1.Arg.isRequired(url, "url"); Utils_1.Arg.isRequired(transferFormat, "transferFormat"); Utils_1.Arg.isIn(transferFormat, ITransport_1.TransferFormat, "transferFormat"); this._logger.log(ILogger_1.LogLevel.Trace, "(WebSockets transport) Connecting."); let token; if (this._accessTokenFactory) { token = await this._accessTokenFactory(); } return new Promise((resolve, reject) => { url = url.replace(/^http/, "ws"); let webSocket; const cookies = this._httpClient.getCookieString(url); let opened = false; if (Utils_1.Platform.isNode || Utils_1.Platform.isReactNative) { const headers = {}; const [name, value] = (0, Utils_1.getUserAgentHeader)(); headers[name] = value; if (token) { headers[HeaderNames_1.HeaderNames.Authorization] = `Bearer ${token}`; } if (cookies) { headers[HeaderNames_1.HeaderNames.Cookie] = cookies; } // Only pass headers when in non-browser environments webSocket = new this._webSocketConstructor(url, undefined, { headers: { ...headers, ...this._headers }, }); } else { if (token) { url += (url.indexOf("?") < 0 ? "?" : "&") + `access_token=${encodeURIComponent(token)}`; } } if (!webSocket) { // Chrome is not happy with passing 'undefined' as protocol webSocket = new this._webSocketConstructor(url); } if (transferFormat === ITransport_1.TransferFormat.Binary) { webSocket.binaryType = "arraybuffer"; } webSocket.onopen = (_event) => { this._logger.log(ILogger_1.LogLevel.Information, `WebSocket connected to ${url}.`); this._webSocket = webSocket; opened = true; resolve(); }; webSocket.onerror = (event) => { let error = null; // ErrorEvent is a browser only type we need to check if the type exists before using it if (typeof ErrorEvent !== "undefined" && event instanceof ErrorEvent) { error = event.error; } else { error = "There was an error with the transport"; } this._logger.log(ILogger_1.LogLevel.Information, `(WebSockets transport) ${error}.`); }; webSocket.onmessage = (message) => { this._logger.log(ILogger_1.LogLevel.Trace, `(WebSockets transport) data received. ${(0, Utils_1.getDataDetail)(message.data, this._logMessageContent)}.`); if (this.onreceive) { try { this.onreceive(message.data); } catch (error) { this._close(error); return; } } }; webSocket.onclose = (event) => { // Don't call close handler if connection was never established // We'll reject the connect call instead if (opened) { this._close(event); } else { let error = null; // ErrorEvent is a browser only type we need to check if the type exists before using it if (typeof ErrorEvent !== "undefined" && event instanceof ErrorEvent) { error = event.error; } else { error = "WebSocket failed to connect. The connection could not be found on the server," + " either the endpoint may not be a SignalR endpoint," + " the connection ID is not present on the server, or there is a proxy blocking WebSockets." + " If you have multiple servers check that sticky sessions are enabled."; } reject(new Error(error)); } }; }); } send(data) { if (this._webSocket && this._webSocket.readyState === this._webSocketConstructor.OPEN) { this._logger.log(ILogger_1.LogLevel.Trace, `(WebSockets transport) sending data. ${(0, Utils_1.getDataDetail)(data, this._logMessageContent)}.`); this._webSocket.send(data); return Promise.resolve(); } return Promise.reject("WebSocket is not in the OPEN state"); } stop() { if (this._webSocket) { // Manually invoke onclose callback inline so we know the HttpConnection was closed properly before returning // This also solves an issue where websocket.onclose could take 18+ seconds to trigger during network disconnects this._close(undefined); } return Promise.resolve(); } _close(event) { // webSocket will be null if the transport did not start successfully if (this._webSocket) { // Clear websocket handlers because we are considering the socket closed now this._webSocket.onclose = () => { }; this._webSocket.onmessage = () => { }; this._webSocket.onerror = () => { }; this._webSocket.close(); this._webSocket = undefined; } this._logger.log(ILogger_1.LogLevel.Trace, "(WebSockets transport) socket closed."); if (this.onclose) { if (this._isCloseEvent(event) && (event.wasClean === false || event.code !== 1000)) { this.onclose(new Error(`WebSocket closed with status code: ${event.code} (${event.reason || "no reason given"}).`)); } else if (event instanceof Error) { this.onclose(event); } else { this.onclose(); } } } _isCloseEvent(event) { return event && typeof event.wasClean === "boolean" && typeof event.code === "number"; } } exports.WebSocketTransport = WebSocketTransport; //# sourceMappingURL=WebSocketTransport.js.map