@microsoft/signalr
Version:
ASP.NET Core SignalR Client
159 lines • 7.54 kB
JavaScript
"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