diff --git a/ASP.NET Core Demo/UnitTests/MockDotNetifyHub.cs b/ASP.NET Core Demo/UnitTests/MockDotNetifyHub.cs index 563e49b1..f47501b1 100644 --- a/ASP.NET Core Demo/UnitTests/MockDotNetifyHub.cs +++ b/ASP.NET Core Demo/UnitTests/MockDotNetifyHub.cs @@ -5,6 +5,8 @@ using DotNetify; using System.Security.Claims; using System.Threading.Tasks; +using Microsoft.AspNetCore.Http.Features; +using System.Threading; namespace UnitTests { @@ -84,6 +86,12 @@ public Task SendAsync(string method, object[] args) Hub.GetType().GetMethod(method).Invoke(Hub, args); return Task.CompletedTask; } + + public Task SendCoreAsync(string method, object[] args) + { + Hub.GetType().GetMethod(method).Invoke(Hub, (object[])args[0]); + return Task.CompletedTask; + } } private class MockHubConnectionContext : HubConnectionContext @@ -100,6 +108,33 @@ private class MockHubConnectionContext : HubConnectionContext public override string ConnectionId => _mockConnectionId; } + private class MockHubCallerContext : HubCallerContext + { + private readonly HubConnectionContext _connectionContext; + + public MockHubCallerContext(HubConnectionContext connectionContext) : base() + { + _connectionContext = connectionContext; + } + + public override string ConnectionId => _connectionContext.ConnectionId; + + public override string UserIdentifier => _connectionContext.User.Identity.Name; + + public override ClaimsPrincipal User => _connectionContext.User; + + public override IDictionary Items => throw new NotImplementedException(); + + public override IFeatureCollection Features => throw new NotImplementedException(); + + public override CancellationToken ConnectionAborted => throw new NotImplementedException(); + + public override void Abort() + { + throw new NotImplementedException(); + } + } + public MockDotNetifyHub Create() { _hub = new DotNetifyHub( @@ -109,7 +144,7 @@ public MockDotNetifyHub Create() new HubPipeline(_middlewareFactories, _vmFilterFactories), new MockHubContext() { MockHubClients = new MockHubClients { MockClientProxy = new MockClientProxy { Hub = this } } }) { - Context = new HubCallerContext(new MockHubConnectionContext(ConnectionId)) + Context = new MockHubCallerContext(new MockHubConnectionContext(ConnectionId)) }; return this; } diff --git a/ASP.NET Core Demo/WebApplication.Knockout/WebApplication.Knockout.csproj b/ASP.NET Core Demo/WebApplication.Knockout/WebApplication.Knockout.csproj index 93262fb2..990d492a 100644 --- a/ASP.NET Core Demo/WebApplication.Knockout/WebApplication.Knockout.csproj +++ b/ASP.NET Core Demo/WebApplication.Knockout/WebApplication.Knockout.csproj @@ -24,7 +24,7 @@ - + diff --git a/ASP.NET Core Demo/WebApplication.Knockout/package.json b/ASP.NET Core Demo/WebApplication.Knockout/package.json index 2fc147dd..b7b1a05a 100644 --- a/ASP.NET Core Demo/WebApplication.Knockout/package.json +++ b/ASP.NET Core Demo/WebApplication.Knockout/package.json @@ -8,7 +8,7 @@ "knockout": "^3.4.1", "requirejs": "^2.3.2", "signalr": "^2.2.2", - "@aspnet/signalr": "1.0.0-preview1-final" + "@aspnet/signalr": "1.0.0-preview2-final" }, "devDependencies": { "grunt": "^1.0.1", @@ -16,4 +16,4 @@ "grunt-contrib-copy": "^1.0.0", "grunt-contrib-uglify": "^2.0.0" } -} +} \ No newline at end of file diff --git a/ASP.NET Core Demo/WebApplication.Knockout/web.config b/ASP.NET Core Demo/WebApplication.Knockout/web.config index dc0514fc..8700b60c 100644 --- a/ASP.NET Core Demo/WebApplication.Knockout/web.config +++ b/ASP.NET Core Demo/WebApplication.Knockout/web.config @@ -1,14 +1,12 @@  - - - + - + - + \ No newline at end of file diff --git a/ASP.NET Core Demo/WebApplication.Knockout/wwwroot/lib/dotnetify.js b/ASP.NET Core Demo/WebApplication.Knockout/wwwroot/lib/dotnetify.js index 466f4f9e..77e6467f 100644 --- a/ASP.NET Core Demo/WebApplication.Knockout/wwwroot/lib/dotnetify.js +++ b/ASP.NET Core Demo/WebApplication.Knockout/wwwroot/lib/dotnetify.js @@ -35,7 +35,7 @@ var dotNetify = {}; dotnetify = $.extend(dotnetify, { - version: "1.1.5", + version: "1.2.0", // SignalR hub options. hub: dotnetifyHub, @@ -65,6 +65,12 @@ var dotNetify = {}; dotnetifyHub.client.response_VM = function (iVMId, iVMData) { + // SignalR .NET Core is sending an array of arguments. + if (Array.isArray(iVMId)) { + iVMData = iVMId[1]; + iVMId = iVMId[0]; + } + // Report unauthorized access. if (iVMData == "403") { console.error("Unauthorized access to " + iVMId); diff --git a/ASP.NET Core Demo/WebApplication.Knockout/wwwroot/lib/dotnetify.router.js b/ASP.NET Core Demo/WebApplication.Knockout/wwwroot/lib/dotnetify.router.js index a75c27bc..98b362e7 100644 --- a/ASP.NET Core Demo/WebApplication.Knockout/wwwroot/lib/dotnetify.router.js +++ b/ASP.NET Core Demo/WebApplication.Knockout/wwwroot/lib/dotnetify.router.js @@ -34,7 +34,7 @@ limitations under the License. // Add plugin functions. dotnetify.router = { - version: "1.1.3", + version: "1.2.0", // URL path that will be parsed when performing routing. urlPath: document.location.pathname, diff --git a/ASP.NET Core Demo/WebApplication.Knockout/wwwroot/lib/signalr.js b/ASP.NET Core Demo/WebApplication.Knockout/wwwroot/lib/signalr.js index af582038..943c1ffb 100644 --- a/ASP.NET Core Demo/WebApplication.Knockout/wwwroot/lib/signalr.js +++ b/ASP.NET Core Demo/WebApplication.Knockout/wwwroot/lib/signalr.js @@ -1,7 +1,7 @@ /* @license * Copyright (c) .NET Foundation. All rights reserved. * Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -*/ + */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : @@ -1228,8 +1228,14 @@ Object.defineProperty(exports, "__esModule", { value: true }); var HttpError = /** @class */ (function (_super) { __extends(HttpError, _super); function HttpError(errorMessage, statusCode) { - var _this = _super.call(this, errorMessage) || this; + var _newTarget = this.constructor; + var _this = this; + var trueProto = _newTarget.prototype; + _this = _super.call(this, errorMessage) || this; _this.statusCode = statusCode; + // Workaround issue in Typescript compiler + // https://github.com/Microsoft/TypeScript/issues/13965#issuecomment-278570200 + _this.__proto__ = trueProto; return _this; } return HttpError; @@ -1238,8 +1244,15 @@ exports.HttpError = HttpError; var TimeoutError = /** @class */ (function (_super) { __extends(TimeoutError, _super); function TimeoutError(errorMessage) { + var _newTarget = this.constructor; if (errorMessage === void 0) { errorMessage = "A timeout occurred."; } - return _super.call(this, errorMessage) || this; + var _this = this; + var trueProto = _newTarget.prototype; + _this = _super.call(this, errorMessage) || this; + // Workaround issue in Typescript compiler + // https://github.com/Microsoft/TypeScript/issues/13965#issuecomment-278570200 + _this.__proto__ = trueProto; + return _this; } return TimeoutError; }(Error)); @@ -1251,6 +1264,22 @@ unwrapExports(Errors); var Errors_1 = Errors.HttpError; var Errors_2 = Errors.TimeoutError; +var ILogger = createCommonjsModule(function (module, exports) { +Object.defineProperty(exports, "__esModule", { value: true }); +var LogLevel; +(function (LogLevel) { + LogLevel[LogLevel["Trace"] = 0] = "Trace"; + LogLevel[LogLevel["Information"] = 1] = "Information"; + LogLevel[LogLevel["Warning"] = 2] = "Warning"; + LogLevel[LogLevel["Error"] = 3] = "Error"; + LogLevel[LogLevel["None"] = 4] = "None"; +})(LogLevel = exports.LogLevel || (exports.LogLevel = {})); + +}); + +unwrapExports(ILogger); +var ILogger_1 = ILogger.LogLevel; + var HttpClient_1 = createCommonjsModule(function (module, exports) { var __extends = (commonjsGlobal && commonjsGlobal.__extends) || (function () { var extendStatics = Object.setPrototypeOf || @@ -1272,6 +1301,7 @@ var __assign = (commonjsGlobal && commonjsGlobal.__assign) || Object.assign || f }; Object.defineProperty(exports, "__esModule", { value: true }); + var HttpResponse = /** @class */ (function () { function HttpResponse(statusCode, statusText, content) { this.statusCode = statusCode; @@ -1295,16 +1325,21 @@ var HttpClient = /** @class */ (function () { exports.HttpClient = HttpClient; var DefaultHttpClient = /** @class */ (function (_super) { __extends(DefaultHttpClient, _super); - function DefaultHttpClient() { - return _super !== null && _super.apply(this, arguments) || this; + function DefaultHttpClient(logger) { + var _this = _super.call(this) || this; + _this.logger = logger; + return _this; } DefaultHttpClient.prototype.send = function (request) { + var _this = this; return new Promise(function (resolve, reject) { var xhr = new XMLHttpRequest(); xhr.open(request.method, request.url, true); + xhr.withCredentials = true; xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest"); if (request.headers) { - request.headers.forEach(function (value, header) { return xhr.setRequestHeader(header, value); }); + Object.keys(request.headers) + .forEach(function (header) { return xhr.setRequestHeader(header, request.headers[header]); }); } if (request.responseType) { xhr.responseType = request.responseType; @@ -1329,9 +1364,11 @@ var DefaultHttpClient = /** @class */ (function (_super) { } }; xhr.onerror = function () { + _this.logger.log(ILogger.LogLevel.Warning, "Error from HTTP request. " + xhr.status + ": " + xhr.statusText); reject(new Errors.HttpError(xhr.statusText, xhr.status)); }; xhr.ontimeout = function () { + _this.logger.log(ILogger.LogLevel.Warning, "Timeout from HTTP request."); reject(new Errors.TimeoutError()); }; xhr.send(request.content || ""); @@ -1348,21 +1385,67 @@ var HttpClient_2 = HttpClient_1.HttpResponse; var HttpClient_3 = HttpClient_1.HttpClient; var HttpClient_4 = HttpClient_1.DefaultHttpClient; -var ILogger = createCommonjsModule(function (module, exports) { +var Loggers = createCommonjsModule(function (module, exports) { Object.defineProperty(exports, "__esModule", { value: true }); -var LogLevel; -(function (LogLevel) { - LogLevel[LogLevel["Trace"] = 0] = "Trace"; - LogLevel[LogLevel["Information"] = 1] = "Information"; - LogLevel[LogLevel["Warning"] = 2] = "Warning"; - LogLevel[LogLevel["Error"] = 3] = "Error"; - LogLevel[LogLevel["None"] = 4] = "None"; -})(LogLevel = exports.LogLevel || (exports.LogLevel = {})); + +var NullLogger = /** @class */ (function () { + function NullLogger() { + } + NullLogger.prototype.log = function (logLevel, message) { + }; + return NullLogger; +}()); +exports.NullLogger = NullLogger; +var ConsoleLogger = /** @class */ (function () { + function ConsoleLogger(minimumLogLevel) { + this.minimumLogLevel = minimumLogLevel; + } + ConsoleLogger.prototype.log = function (logLevel, message) { + if (logLevel >= this.minimumLogLevel) { + switch (logLevel) { + case ILogger.LogLevel.Error: + console.error(ILogger.LogLevel[logLevel] + ": " + message); + break; + case ILogger.LogLevel.Warning: + console.warn(ILogger.LogLevel[logLevel] + ": " + message); + break; + case ILogger.LogLevel.Information: + console.info(ILogger.LogLevel[logLevel] + ": " + message); + break; + default: + console.log(ILogger.LogLevel[logLevel] + ": " + message); + break; + } + } + }; + return ConsoleLogger; +}()); +exports.ConsoleLogger = ConsoleLogger; +var LoggerFactory = /** @class */ (function () { + function LoggerFactory() { + } + LoggerFactory.createLogger = function (logging) { + if (logging === undefined) { + return new ConsoleLogger(ILogger.LogLevel.Information); + } + if (logging === null) { + return new NullLogger(); + } + if (logging.log) { + return logging; + } + return new ConsoleLogger(logging); + }; + return LoggerFactory; +}()); +exports.LoggerFactory = LoggerFactory; }); -unwrapExports(ILogger); -var ILogger_1 = ILogger.LogLevel; +unwrapExports(Loggers); +var Loggers_1 = Loggers.NullLogger; +var Loggers_2 = Loggers.ConsoleLogger; +var Loggers_3 = Loggers.LoggerFactory; var AbortController_1 = createCommonjsModule(function (module, exports) { Object.defineProperty(exports, "__esModule", { value: true }); @@ -1404,6 +1487,31 @@ exports.AbortController = AbortController; unwrapExports(AbortController_1); var AbortController_2 = AbortController_1.AbortController; +var Utils = createCommonjsModule(function (module, exports) { +Object.defineProperty(exports, "__esModule", { value: true }); +var Arg = /** @class */ (function () { + function Arg() { + } + Arg.isRequired = function (val, name) { + if (val === null || val === undefined) { + throw new Error("The '" + name + "' argument is required."); + } + }; + Arg.isIn = function (val, values, name) { + // TypeScript enums have keys for **both** the name and the value of each enum member on the type itself. + if (!(val in values)) { + throw new Error("Unknown " + name + " value: " + val + "."); + } + }; + return Arg; +}()); +exports.Arg = Arg; + +}); + +unwrapExports(Utils); +var Utils_1 = Utils.Arg; + var Transports = createCommonjsModule(function (module, exports) { var __awaiter = (commonjsGlobal && commonjsGlobal.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { @@ -1444,19 +1552,33 @@ Object.defineProperty(exports, "__esModule", { value: true }); + var TransportType; (function (TransportType) { TransportType[TransportType["WebSockets"] = 0] = "WebSockets"; TransportType[TransportType["ServerSentEvents"] = 1] = "ServerSentEvents"; TransportType[TransportType["LongPolling"] = 2] = "LongPolling"; })(TransportType = exports.TransportType || (exports.TransportType = {})); +var TransferFormat; +(function (TransferFormat) { + TransferFormat[TransferFormat["Text"] = 1] = "Text"; + TransferFormat[TransferFormat["Binary"] = 2] = "Binary"; +})(TransferFormat = exports.TransferFormat || (exports.TransferFormat = {})); var WebSocketTransport = /** @class */ (function () { function WebSocketTransport(accessTokenFactory, logger) { this.logger = logger; this.accessTokenFactory = accessTokenFactory || (function () { return null; }); } - WebSocketTransport.prototype.connect = function (url, requestedTransferMode, connection) { + WebSocketTransport.prototype.connect = function (url, transferFormat, connection) { var _this = this; + Utils.Arg.isRequired(url, "url"); + Utils.Arg.isRequired(transferFormat, "transferFormat"); + Utils.Arg.isIn(transferFormat, TransferFormat, "transferFormat"); + Utils.Arg.isRequired(connection, "connection"); + if (typeof (WebSocket) === "undefined") { + throw new Error("'WebSocket' is not supported in your environment."); + } + this.logger.log(ILogger.LogLevel.Trace, "(WebSockets transport) Connecting"); return new Promise(function (resolve, reject) { url = url.replace(/^http/, "ws"); var token = _this.accessTokenFactory(); @@ -1464,19 +1586,19 @@ var WebSocketTransport = /** @class */ (function () { url += (url.indexOf("?") < 0 ? "?" : "&") + ("access_token=" + encodeURIComponent(token)); } var webSocket = new WebSocket(url); - if (requestedTransferMode == 2 /* Binary */) { + if (transferFormat === TransferFormat.Binary) { webSocket.binaryType = "arraybuffer"; } webSocket.onopen = function (event) { _this.logger.log(ILogger.LogLevel.Information, "WebSocket connected to " + url); _this.webSocket = webSocket; - resolve(requestedTransferMode); + resolve(); }; webSocket.onerror = function (event) { - reject(); + reject(event.error); }; webSocket.onmessage = function (message) { - _this.logger.log(ILogger.LogLevel.Trace, "(WebSockets transport) data received: " + message.data); + _this.logger.log(ILogger.LogLevel.Trace, "(WebSockets transport) data received. " + getDataDetail(message.data) + "."); if (_this.onreceive) { _this.onreceive(message.data); } @@ -1496,6 +1618,7 @@ var WebSocketTransport = /** @class */ (function () { }; WebSocketTransport.prototype.send = function (data) { if (this.webSocket && this.webSocket.readyState === WebSocket.OPEN) { + this.logger.log(ILogger.LogLevel.Trace, "(WebSockets transport) sending data. " + getDataDetail(data) + "."); this.webSocket.send(data); return Promise.resolve(); } @@ -1517,23 +1640,31 @@ var ServerSentEventsTransport = /** @class */ (function () { this.accessTokenFactory = accessTokenFactory || (function () { return null; }); this.logger = logger; } - ServerSentEventsTransport.prototype.connect = function (url, requestedTransferMode, connection) { + ServerSentEventsTransport.prototype.connect = function (url, transferFormat, connection) { var _this = this; + Utils.Arg.isRequired(url, "url"); + Utils.Arg.isRequired(transferFormat, "transferFormat"); + Utils.Arg.isIn(transferFormat, TransferFormat, "transferFormat"); + Utils.Arg.isRequired(connection, "connection"); if (typeof (EventSource) === "undefined") { - Promise.reject("EventSource not supported by the browser."); + throw new Error("'EventSource' is not supported in your environment."); } + this.logger.log(ILogger.LogLevel.Trace, "(SSE transport) Connecting"); this.url = url; return new Promise(function (resolve, reject) { + if (transferFormat !== TransferFormat.Text) { + reject(new Error("The Server-Sent Events transport only supports the 'Text' transfer format")); + } var token = _this.accessTokenFactory(); if (token) { url += (url.indexOf("?") < 0 ? "?" : "&") + ("access_token=" + encodeURIComponent(token)); } - var eventSource = new EventSource(url); + var eventSource = new EventSource(url, { withCredentials: true }); try { eventSource.onmessage = function (e) { if (_this.onreceive) { try { - _this.logger.log(ILogger.LogLevel.Trace, "(SSE transport) data received: " + e.data); + _this.logger.log(ILogger.LogLevel.Trace, "(SSE transport) data received. " + getDataDetail(e.data) + "."); _this.onreceive(e.data); } catch (error) { @@ -1545,7 +1676,7 @@ var ServerSentEventsTransport = /** @class */ (function () { } }; eventSource.onerror = function (e) { - reject(); + reject(new Error(e.message || "Error occurred")); // don't report an error if the transport did not start successfully if (_this.eventSource && _this.onclose) { _this.onclose(new Error(e.message || "Error occurred")); @@ -1555,7 +1686,7 @@ var ServerSentEventsTransport = /** @class */ (function () { _this.logger.log(ILogger.LogLevel.Information, "SSE connected to " + _this.url); _this.eventSource = eventSource; // SSE is a text protocol - resolve(1 /* Text */); + resolve(); }; } catch (e) { @@ -1566,7 +1697,7 @@ var ServerSentEventsTransport = /** @class */ (function () { ServerSentEventsTransport.prototype.send = function (data) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { - return [2 /*return*/, send(this.httpClient, this.url, this.accessTokenFactory, data)]; + return [2 /*return*/, send(this.logger, "SSE", this.httpClient, this.url, this.accessTokenFactory, data)]; }); }); }; @@ -1587,34 +1718,40 @@ var LongPollingTransport = /** @class */ (function () { this.logger = logger; this.pollAbort = new AbortController_1.AbortController(); } - LongPollingTransport.prototype.connect = function (url, requestedTransferMode, connection) { + LongPollingTransport.prototype.connect = function (url, transferFormat, connection) { + Utils.Arg.isRequired(url, "url"); + Utils.Arg.isRequired(transferFormat, "transferFormat"); + Utils.Arg.isIn(transferFormat, TransferFormat, "transferFormat"); + Utils.Arg.isRequired(connection, "connection"); this.url = url; + this.logger.log(ILogger.LogLevel.Trace, "(LongPolling transport) Connecting"); // Set a flag indicating we have inherent keep-alive in this transport. connection.features.inherentKeepAlive = true; - if (requestedTransferMode === 2 /* Binary */ && (typeof new XMLHttpRequest().responseType !== "string")) { + if (transferFormat === TransferFormat.Binary && (typeof new XMLHttpRequest().responseType !== "string")) { // This will work if we fix: https://github.com/aspnet/SignalR/issues/742 throw new Error("Binary protocols over XmlHttpRequest not implementing advanced features are not supported."); } - this.poll(this.url, requestedTransferMode); - return Promise.resolve(requestedTransferMode); + this.poll(this.url, transferFormat); + return Promise.resolve(); }; - LongPollingTransport.prototype.poll = function (url, transferMode) { + LongPollingTransport.prototype.poll = function (url, transferFormat) { return __awaiter(this, void 0, void 0, function () { var pollOptions, token, pollUrl, response, e_1; return __generator(this, function (_a) { switch (_a.label) { case 0: pollOptions = { - timeout: 120000, abortSignal: this.pollAbort.signal, - headers: new Map(), + headers: {}, + timeout: 90000, }; - if (transferMode === 2 /* Binary */) { + if (transferFormat === TransferFormat.Binary) { pollOptions.responseType = "arraybuffer"; } token = this.accessTokenFactory(); if (token) { - pollOptions.headers.set("Authorization", "Bearer " + token); + // tslint:disable-next-line:no-string-literal + pollOptions.headers["Authorization"] = "Bearer " + token; } _a.label = 1; case 1: @@ -1646,7 +1783,7 @@ var LongPollingTransport = /** @class */ (function () { else { // Process the response if (response.content) { - this.logger.log(ILogger.LogLevel.Trace, "(LongPolling transport) data received: " + response.content); + this.logger.log(ILogger.LogLevel.Trace, "(LongPolling transport) data received. " + getDataDetail(response.content) + "."); if (this.onreceive) { this.onreceive(response.content); } @@ -1680,7 +1817,7 @@ var LongPollingTransport = /** @class */ (function () { LongPollingTransport.prototype.send = function (data) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { - return [2 /*return*/, send(this.httpClient, this.url, this.accessTokenFactory, data)]; + return [2 /*return*/, send(this.logger, "LongPolling", this.httpClient, this.url, this.accessTokenFactory, data)]; }); }); }; @@ -1691,23 +1828,34 @@ var LongPollingTransport = /** @class */ (function () { return LongPollingTransport; }()); exports.LongPollingTransport = LongPollingTransport; -function send(httpClient, url, accessTokenFactory, content) { +function getDataDetail(data) { + var length = null; + if (data instanceof ArrayBuffer) { + length = "Binary data of length " + data.byteLength; + } + else if (typeof data === "string") { + length = "String data of length " + data.length; + } + return length; +} +function send(logger, transportName, httpClient, url, accessTokenFactory, content) { return __awaiter(this, void 0, void 0, function () { - var headers, token; - return __generator(this, function (_a) { - switch (_a.label) { + var headers, token, response, _a; + return __generator(this, function (_b) { + switch (_b.label) { case 0: token = accessTokenFactory(); if (token) { - headers = new Map(); - headers.set("Authorization", "Bearer " + accessTokenFactory()); + headers = (_a = {}, _a["Authorization"] = "Bearer " + accessTokenFactory(), _a); } + logger.log(ILogger.LogLevel.Trace, "(" + transportName + " transport) sending data. " + getDataDetail(content) + "."); return [4 /*yield*/, httpClient.post(url, { content: content, - headers: headers + headers: headers, })]; case 1: - _a.sent(); + response = _b.sent(); + logger.log(ILogger.LogLevel.Trace, "(" + transportName + " transport) request complete. Response status: " + response.statusCode + "."); return [2 /*return*/]; } }); @@ -1718,69 +1866,10 @@ function send(httpClient, url, accessTokenFactory, content) { unwrapExports(Transports); var Transports_1 = Transports.TransportType; -var Transports_2 = Transports.WebSocketTransport; -var Transports_3 = Transports.ServerSentEventsTransport; -var Transports_4 = Transports.LongPollingTransport; - -var Loggers = createCommonjsModule(function (module, exports) { -Object.defineProperty(exports, "__esModule", { value: true }); - -var NullLogger = /** @class */ (function () { - function NullLogger() { - } - NullLogger.prototype.log = function (logLevel, message) { - }; - return NullLogger; -}()); -exports.NullLogger = NullLogger; -var ConsoleLogger = /** @class */ (function () { - function ConsoleLogger(minimumLogLevel) { - this.minimumLogLevel = minimumLogLevel; - } - ConsoleLogger.prototype.log = function (logLevel, message) { - if (logLevel >= this.minimumLogLevel) { - switch (logLevel) { - case ILogger.LogLevel.Error: - console.error(ILogger.LogLevel[logLevel] + ": " + message); - break; - case ILogger.LogLevel.Warning: - console.warn(ILogger.LogLevel[logLevel] + ": " + message); - break; - case ILogger.LogLevel.Information: - console.info(ILogger.LogLevel[logLevel] + ": " + message); - break; - default: - console.log(ILogger.LogLevel[logLevel] + ": " + message); - break; - } - } - }; - return ConsoleLogger; -}()); -exports.ConsoleLogger = ConsoleLogger; -var LoggerFactory; -(function (LoggerFactory) { - function createLogger(logging) { - if (logging === undefined) { - return new ConsoleLogger(ILogger.LogLevel.Information); - } - if (logging === null) { - return new NullLogger(); - } - if (logging.log) { - return logging; - } - return new ConsoleLogger(logging); - } - LoggerFactory.createLogger = createLogger; -})(LoggerFactory = exports.LoggerFactory || (exports.LoggerFactory = {})); - -}); - -unwrapExports(Loggers); -var Loggers_1 = Loggers.NullLogger; -var Loggers_2 = Loggers.ConsoleLogger; -var Loggers_3 = Loggers.LoggerFactory; +var Transports_2 = Transports.TransferFormat; +var Transports_3 = Transports.WebSocketTransport; +var Transports_4 = Transports.ServerSentEventsTransport; +var Transports_5 = Transports.LongPollingTransport; var HttpConnection_1 = createCommonjsModule(function (module, exports) { var __awaiter = (commonjsGlobal && commonjsGlobal.__awaiter) || function (thisArg, _arguments, P, generator) { @@ -1823,131 +1912,231 @@ Object.defineProperty(exports, "__esModule", { value: true }); + var HttpConnection = /** @class */ (function () { function HttpConnection(url, options) { if (options === void 0) { options = {}; } this.features = {}; + Utils.Arg.isRequired(url, "url"); this.logger = Loggers.LoggerFactory.createLogger(options.logger); this.baseUrl = this.resolveUrl(url); options = options || {}; options.accessTokenFactory = options.accessTokenFactory || (function () { return null; }); - this.httpClient = options.httpClient || new HttpClient_1.DefaultHttpClient(); + this.httpClient = options.httpClient || new HttpClient_1.DefaultHttpClient(this.logger); this.connectionState = 2 /* Disconnected */; this.options = options; } - HttpConnection.prototype.start = function () { - return __awaiter(this, void 0, void 0, function () { - return __generator(this, function (_a) { - if (this.connectionState !== 2 /* Disconnected */) { - return [2 /*return*/, Promise.reject(new Error("Cannot start a connection that is not in the 'Disconnected' state."))]; - } - this.connectionState = 0 /* Connecting */; - this.startPromise = this.startInternal(); - return [2 /*return*/, this.startPromise]; - }); - }); + HttpConnection.prototype.start = function (transferFormat) { + Utils.Arg.isRequired(transferFormat, "transferFormat"); + Utils.Arg.isIn(transferFormat, Transports.TransferFormat, "transferFormat"); + this.logger.log(ILogger.LogLevel.Trace, "Starting connection with transfer format '" + Transports.TransferFormat[transferFormat] + "'."); + if (this.connectionState !== 2 /* Disconnected */) { + return Promise.reject(new Error("Cannot start a connection that is not in the 'Disconnected' state.")); + } + this.connectionState = 0 /* Connecting */; + this.startPromise = this.startInternal(transferFormat); + return this.startPromise; }; - HttpConnection.prototype.startInternal = function () { + HttpConnection.prototype.startInternal = function (transferFormat) { return __awaiter(this, void 0, void 0, function () { var _this = this; - var headers, token, negotiatePayload, negotiateResponse, requestedTransferMode, _a, e_1; + var token, headers, negotiateResponse, e_1, _a; return __generator(this, function (_b) { switch (_b.label) { case 0: - _b.trys.push([0, 5, , 6]); - if (!(this.options.transport === Transports.TransportType.WebSockets)) return [3 /*break*/, 1]; + _b.trys.push([0, 6, , 7]); + if (!(this.options.transport === Transports.TransportType.WebSockets)) return [3 /*break*/, 2]; // No need to add a connection ID in this case this.url = this.baseUrl; - this.transport = this.createTransport(this.options.transport, [Transports.TransportType[Transports.TransportType.WebSockets]]); - return [3 /*break*/, 3]; + this.transport = this.constructTransport(Transports.TransportType.WebSockets); + // We should just call connect directly in this case. + // No fallback or negotiate in this case. + return [4 /*yield*/, this.transport.connect(this.url, transferFormat, this)]; case 1: - headers = void 0; + // We should just call connect directly in this case. + // No fallback or negotiate in this case. + _b.sent(); + return [3 /*break*/, 5]; + case 2: token = this.options.accessTokenFactory(); + headers = void 0; if (token) { - headers = new Map(); - headers.set("Authorization", "Bearer " + token); + headers = (_a = {}, _a["Authorization"] = "Bearer " + token, _a); } - return [4 /*yield*/, this.httpClient.post(this.resolveNegotiateUrl(this.baseUrl), { - content: "", - headers: headers - })]; - case 2: - negotiatePayload = _b.sent(); - negotiateResponse = JSON.parse(negotiatePayload.content); - this.connectionId = negotiateResponse.connectionId; + return [4 /*yield*/, this.getNegotiationResponse(headers)]; + case 3: + negotiateResponse = _b.sent(); // the user tries to stop the the connection when it is being started - if (this.connectionState == 2 /* Disconnected */) { + if (this.connectionState === 2 /* Disconnected */) { return [2 /*return*/]; } - if (this.connectionId) { - this.url = this.baseUrl + (this.baseUrl.indexOf("?") === -1 ? "?" : "&") + ("id=" + this.connectionId); - this.transport = this.createTransport(this.options.transport, negotiateResponse.availableTransports); - } - _b.label = 3; - case 3: + return [4 /*yield*/, this.createTransport(this.options.transport, negotiateResponse, transferFormat, headers)]; + case 4: + _b.sent(); + _b.label = 5; + case 5: this.transport.onreceive = this.onreceive; this.transport.onclose = function (e) { return _this.stopConnection(true, e); }; - requestedTransferMode = this.features.transferMode === 2 /* Binary */ - ? 2 /* Binary */ - : 1 /* Text */; - _a = this.features; - return [4 /*yield*/, this.transport.connect(this.url, requestedTransferMode, this)]; - case 4: - _a.transferMode = _b.sent(); // only change the state if we were connecting to not overwrite // the state if the connection is already marked as Disconnected this.changeState(0 /* Connecting */, 1 /* Connected */); - return [3 /*break*/, 6]; - case 5: + return [3 /*break*/, 7]; + case 6: e_1 = _b.sent(); - this.logger.log(ILogger.LogLevel.Error, "Failed to start the connection. " + e_1); + this.logger.log(ILogger.LogLevel.Error, "Failed to start the connection: " + e_1); this.connectionState = 2 /* Disconnected */; this.transport = null; throw e_1; + case 7: return [2 /*return*/]; + } + }); + }); + }; + HttpConnection.prototype.getNegotiationResponse = function (headers) { + return __awaiter(this, void 0, void 0, function () { + var negotiateUrl, response, e_2; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + negotiateUrl = this.resolveNegotiateUrl(this.baseUrl); + this.logger.log(ILogger.LogLevel.Trace, "Sending negotiation request: " + negotiateUrl); + _a.label = 1; + case 1: + _a.trys.push([1, 3, , 4]); + return [4 /*yield*/, this.httpClient.post(negotiateUrl, { + content: "", + headers: headers, + })]; + case 2: + response = _a.sent(); + return [2 /*return*/, JSON.parse(response.content)]; + case 3: + e_2 = _a.sent(); + this.logger.log(ILogger.LogLevel.Error, "Failed to complete negotiation with the server: " + e_2); + throw e_2; + case 4: return [2 /*return*/]; + } + }); + }); + }; + HttpConnection.prototype.updateConnectionId = function (negotiateResponse) { + this.connectionId = negotiateResponse.connectionId; + this.url = this.baseUrl + (this.baseUrl.indexOf("?") === -1 ? "?" : "&") + ("id=" + this.connectionId); + }; + HttpConnection.prototype.createTransport = function (requestedTransport, negotiateResponse, requestedTransferFormat, headers) { + return __awaiter(this, void 0, void 0, function () { + var transports, _i, transports_1, endpoint, transport, ex_1; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + this.updateConnectionId(negotiateResponse); + if (!this.isITransport(requestedTransport)) return [3 /*break*/, 2]; + this.logger.log(ILogger.LogLevel.Trace, "Connection was provided an instance of ITransport, using that directly."); + this.transport = requestedTransport; + return [4 /*yield*/, this.transport.connect(this.url, requestedTransferFormat, this)]; + case 1: + _a.sent(); + // only change the state if we were connecting to not overwrite + // the state if the connection is already marked as Disconnected + this.changeState(0 /* Connecting */, 1 /* Connected */); + return [2 /*return*/]; + case 2: + transports = negotiateResponse.availableTransports; + _i = 0, transports_1 = transports; + _a.label = 3; + case 3: + if (!(_i < transports_1.length)) return [3 /*break*/, 9]; + endpoint = transports_1[_i]; + this.connectionState = 0 /* Connecting */; + transport = this.resolveTransport(endpoint, requestedTransport, requestedTransferFormat); + if (!(typeof transport === "number")) return [3 /*break*/, 8]; + this.transport = this.constructTransport(transport); + if (!(negotiateResponse.connectionId === null)) return [3 /*break*/, 5]; + return [4 /*yield*/, this.getNegotiationResponse(headers)]; + case 4: + negotiateResponse = _a.sent(); + this.updateConnectionId(negotiateResponse); + _a.label = 5; + case 5: + _a.trys.push([5, 7, , 8]); + return [4 /*yield*/, this.transport.connect(this.url, requestedTransferFormat, this)]; case 6: - ; + _a.sent(); + this.changeState(0 /* Connecting */, 1 /* Connected */); return [2 /*return*/]; + case 7: + ex_1 = _a.sent(); + this.logger.log(ILogger.LogLevel.Error, "Failed to start the transport '" + Transports.TransportType[transport] + "': " + ex_1); + this.connectionState = 2 /* Disconnected */; + negotiateResponse.connectionId = null; + return [3 /*break*/, 8]; + case 8: + _i++; + return [3 /*break*/, 3]; + case 9: throw new Error("Unable to initialize any of the available transports."); } }); }); }; - HttpConnection.prototype.createTransport = function (transport, availableTransports) { - if ((transport === null || transport === undefined) && availableTransports.length > 0) { - transport = Transports.TransportType[availableTransports[0]]; + HttpConnection.prototype.constructTransport = function (transport) { + switch (transport) { + case Transports.TransportType.WebSockets: + return new Transports.WebSocketTransport(this.options.accessTokenFactory, this.logger); + case Transports.TransportType.ServerSentEvents: + return new Transports.ServerSentEventsTransport(this.httpClient, this.options.accessTokenFactory, this.logger); + case Transports.TransportType.LongPolling: + return new Transports.LongPollingTransport(this.httpClient, this.options.accessTokenFactory, this.logger); + default: + throw new Error("Unknown transport: " + transport + "."); } - if (transport === Transports.TransportType.WebSockets && availableTransports.indexOf(Transports.TransportType[transport]) >= 0) { - return new Transports.WebSocketTransport(this.options.accessTokenFactory, this.logger); - } - if (transport === Transports.TransportType.ServerSentEvents && availableTransports.indexOf(Transports.TransportType[transport]) >= 0) { - return new Transports.ServerSentEventsTransport(this.httpClient, this.options.accessTokenFactory, this.logger); - } - if (transport === Transports.TransportType.LongPolling && availableTransports.indexOf(Transports.TransportType[transport]) >= 0) { - return new Transports.LongPollingTransport(this.httpClient, this.options.accessTokenFactory, this.logger); + }; + HttpConnection.prototype.resolveTransport = function (endpoint, requestedTransport, requestedTransferFormat) { + var transport = Transports.TransportType[endpoint.transport]; + if (transport === null || transport === undefined) { + this.logger.log(ILogger.LogLevel.Trace, "Skipping transport '" + endpoint.transport + "' because it is not supported by this client."); } - if (this.isITransport(transport)) { - return transport; + else { + var transferFormats = endpoint.transferFormats.map(function (s) { return Transports.TransferFormat[s]; }); + if (!requestedTransport || transport === requestedTransport) { + if (transferFormats.indexOf(requestedTransferFormat) >= 0) { + if ((transport === Transports.TransportType.WebSockets && typeof WebSocket === "undefined") || + (transport === Transports.TransportType.ServerSentEvents && typeof EventSource === "undefined")) { + this.logger.log(ILogger.LogLevel.Trace, "Skipping transport '" + Transports.TransportType[transport] + "' because it is not supported in your environment.'"); + } + else { + this.logger.log(ILogger.LogLevel.Trace, "Selecting transport '" + Transports.TransportType[transport] + "'"); + return transport; + } + } + else { + this.logger.log(ILogger.LogLevel.Trace, "Skipping transport '" + Transports.TransportType[transport] + "' because it does not support the requested transfer format '" + Transports.TransferFormat[requestedTransferFormat] + "'."); + } + } + else { + this.logger.log(ILogger.LogLevel.Trace, "Skipping transport '" + Transports.TransportType[transport] + "' because it was disabled by the client."); + } } - throw new Error("No available transports found."); + return null; }; HttpConnection.prototype.isITransport = function (transport) { return typeof (transport) === "object" && "connect" in transport; }; HttpConnection.prototype.changeState = function (from, to) { - if (this.connectionState == from) { + if (this.connectionState === from) { this.connectionState = to; return true; } return false; }; HttpConnection.prototype.send = function (data) { - if (this.connectionState != 1 /* Connected */) { - throw new Error("Cannot send data if the connection is not in the 'Connected' State"); + if (this.connectionState !== 1 /* Connected */) { + throw new Error("Cannot send data if the connection is not in the 'Connected' State."); } return this.transport.send(data); }; HttpConnection.prototype.stop = function (error) { return __awaiter(this, void 0, void 0, function () { - var previousState, e_2; + var previousState, e_3; return __generator(this, function (_a) { switch (_a.label) { case 0: @@ -1961,10 +2150,10 @@ var HttpConnection = /** @class */ (function () { _a.sent(); return [3 /*break*/, 4]; case 3: - e_2 = _a.sent(); + e_3 = _a.sent(); return [3 /*break*/, 4]; case 4: - this.stopConnection(/*raiseClosed*/ previousState == 1 /* Connected */, error); + this.stopConnection(/*raiseClosed*/ previousState === 1 /* Connected */, error); return [2 /*return*/]; } }); @@ -1991,7 +2180,7 @@ var HttpConnection = /** @class */ (function () { if (url.lastIndexOf("https://", 0) === 0 || url.lastIndexOf("http://", 0) === 0) { return url; } - if (typeof window === 'undefined' || !window || !window.document) { + if (typeof window === "undefined" || !window || !window.document) { throw new Error("Cannot resolve '" + url + "'."); } var parser = window.document.createElement("a"); @@ -1999,11 +2188,11 @@ var HttpConnection = /** @class */ (function () { var baseUrl = (!parser.protocol || parser.protocol === ":") ? window.document.location.protocol + "//" + (parser.host || window.document.location.host) : parser.protocol + "//" + parser.host; - if (!url || url[0] != '/') { - url = '/' + url; + if (!url || url[0] !== "/") { + url = "/" + url; } var normalizedUrl = baseUrl + url; - this.logger.log(ILogger.LogLevel.Information, "Normalizing '" + url + "' to '" + normalizedUrl + "'"); + this.logger.log(ILogger.LogLevel.Information, "Normalizing '" + url + "' to '" + normalizedUrl + "'."); return normalizedUrl; }; HttpConnection.prototype.resolveNegotiateUrl = function (url) { @@ -2025,66 +2214,6 @@ exports.HttpConnection = HttpConnection; unwrapExports(HttpConnection_1); var HttpConnection_2 = HttpConnection_1.HttpConnection; -var Observable = createCommonjsModule(function (module, exports) { -Object.defineProperty(exports, "__esModule", { value: true }); -var Subscription = /** @class */ (function () { - function Subscription(subject, observer) { - this.subject = subject; - this.observer = observer; - } - Subscription.prototype.dispose = function () { - var index = this.subject.observers.indexOf(this.observer); - if (index > -1) { - this.subject.observers.splice(index, 1); - } - if (this.subject.observers.length === 0) { - this.subject.cancelCallback().catch(function (_) { }); - } - }; - return Subscription; -}()); -exports.Subscription = Subscription; -var Subject = /** @class */ (function () { - function Subject(cancelCallback) { - this.observers = []; - this.cancelCallback = cancelCallback; - } - Subject.prototype.next = function (item) { - for (var _i = 0, _a = this.observers; _i < _a.length; _i++) { - var observer = _a[_i]; - observer.next(item); - } - }; - Subject.prototype.error = function (err) { - for (var _i = 0, _a = this.observers; _i < _a.length; _i++) { - var observer = _a[_i]; - if (observer.error) { - observer.error(err); - } - } - }; - Subject.prototype.complete = function () { - for (var _i = 0, _a = this.observers; _i < _a.length; _i++) { - var observer = _a[_i]; - if (observer.complete) { - observer.complete(); - } - } - }; - Subject.prototype.subscribe = function (observer) { - this.observers.push(observer); - return new Subscription(this, observer); - }; - return Subject; -}()); -exports.Subject = Subject; - -}); - -unwrapExports(Observable); -var Observable_1 = Observable.Subscription; -var Observable_2 = Observable.Subject; - var TextMessageFormat_1 = createCommonjsModule(function (module, exports) { Object.defineProperty(exports, "__esModule", { value: true }); var TextMessageFormat = /** @class */ (function () { @@ -2094,14 +2223,15 @@ var TextMessageFormat = /** @class */ (function () { return "" + output + TextMessageFormat.RecordSeparator; }; TextMessageFormat.parse = function (input) { - if (input[input.length - 1] != TextMessageFormat.RecordSeparator) { + if (input[input.length - 1] !== TextMessageFormat.RecordSeparator) { throw new Error("Message is incomplete."); } var messages = input.split(TextMessageFormat.RecordSeparator); messages.pop(); return messages; }; - TextMessageFormat.RecordSeparator = String.fromCharCode(0x1e); + TextMessageFormat.RecordSeparatorCode = 0x1e; + TextMessageFormat.RecordSeparator = String.fromCharCode(TextMessageFormat.RecordSeparatorCode); return TextMessageFormat; }()); exports.TextMessageFormat = TextMessageFormat; @@ -2114,27 +2244,86 @@ var TextMessageFormat_2 = TextMessageFormat_1.TextMessageFormat; var JsonHubProtocol_1 = createCommonjsModule(function (module, exports) { Object.defineProperty(exports, "__esModule", { value: true }); + + + exports.JSON_HUB_PROTOCOL_NAME = "json"; var JsonHubProtocol = /** @class */ (function () { function JsonHubProtocol() { this.name = exports.JSON_HUB_PROTOCOL_NAME; - this.type = 1 /* Text */; + this.version = 1; + this.transferFormat = Transports.TransferFormat.Text; } - JsonHubProtocol.prototype.parseMessages = function (input) { + JsonHubProtocol.prototype.parseMessages = function (input, logger) { if (!input) { return []; } + if (logger === null) { + logger = new Loggers.NullLogger(); + } // Parse the messages var messages = TextMessageFormat_1.TextMessageFormat.parse(input); var hubMessages = []; - for (var i = 0; i < messages.length; ++i) { - hubMessages.push(JSON.parse(messages[i])); + for (var _i = 0, messages_1 = messages; _i < messages_1.length; _i++) { + var message = messages_1[_i]; + var parsedMessage = JSON.parse(message); + if (typeof parsedMessage.type !== "number") { + throw new Error("Invalid payload."); + } + switch (parsedMessage.type) { + case 1 /* Invocation */: + this.isInvocationMessage(parsedMessage); + break; + case 2 /* StreamItem */: + this.isStreamItemMessage(parsedMessage); + break; + case 3 /* Completion */: + this.isCompletionMessage(parsedMessage); + break; + case 6 /* Ping */: + // Single value, no need to validate + break; + case 7 /* Close */: + // All optional values, no need to validate + break; + default: + // Future protocol changes can add message types, old clients can ignore them + logger.log(ILogger.LogLevel.Information, "Unknown message type '" + parsedMessage.type + "' ignored."); + continue; + } + hubMessages.push(parsedMessage); } return hubMessages; }; JsonHubProtocol.prototype.writeMessage = function (message) { return TextMessageFormat_1.TextMessageFormat.write(JSON.stringify(message)); }; + JsonHubProtocol.prototype.isInvocationMessage = function (message) { + this.assertNotEmptyString(message.target, "Invalid payload for Invocation message."); + if (message.invocationId !== undefined) { + this.assertNotEmptyString(message.invocationId, "Invalid payload for Invocation message."); + } + }; + JsonHubProtocol.prototype.isStreamItemMessage = function (message) { + this.assertNotEmptyString(message.invocationId, "Invalid payload for StreamItem message."); + if (message.item === undefined) { + throw new Error("Invalid payload for StreamItem message."); + } + }; + JsonHubProtocol.prototype.isCompletionMessage = function (message) { + if (message.result && message.error) { + throw new Error("Invalid payload for Completion message."); + } + if (!message.result && message.error) { + this.assertNotEmptyString(message.error, "Invalid payload for Completion message."); + } + this.assertNotEmptyString(message.invocationId, "Invalid payload for Completion message."); + }; + JsonHubProtocol.prototype.assertNotEmptyString = function (value, errorMessage) { + if (typeof value !== "string" || value === "") { + throw new Error(errorMessage); + } + }; return JsonHubProtocol; }()); exports.JsonHubProtocol = JsonHubProtocol; @@ -2145,58 +2334,65 @@ unwrapExports(JsonHubProtocol_1); var JsonHubProtocol_2 = JsonHubProtocol_1.JSON_HUB_PROTOCOL_NAME; var JsonHubProtocol_3 = JsonHubProtocol_1.JsonHubProtocol; -var Base64EncodedHubProtocol_1 = createCommonjsModule(function (module, exports) { +var Observable = createCommonjsModule(function (module, exports) { Object.defineProperty(exports, "__esModule", { value: true }); -var Base64EncodedHubProtocol = /** @class */ (function () { - function Base64EncodedHubProtocol(protocol) { - this.wrappedProtocol = protocol; - this.name = this.wrappedProtocol.name; - this.type = 1 /* Text */; +var Subscription = /** @class */ (function () { + function Subscription(subject, observer) { + this.subject = subject; + this.observer = observer; } - Base64EncodedHubProtocol.prototype.parseMessages = function (input) { - // The format of the message is `size:message;` - var pos = input.indexOf(":"); - if (pos == -1 || input[input.length - 1] != ';') { - throw new Error("Invalid payload."); - } - var lenStr = input.substring(0, pos); - if (!/^[0-9]+$/.test(lenStr)) { - throw new Error("Invalid length: '" + lenStr + "'"); - } - var messageSize = parseInt(lenStr, 10); - // 2 accounts for ':' after message size and trailing ';' - if (messageSize != input.length - pos - 2) { - throw new Error("Invalid message size."); - } - var encodedMessage = input.substring(pos + 1, input.length - 1); - // atob/btoa are browsers APIs but they can be polyfilled. If this becomes problematic we can use - // base64-js module - var s = atob(encodedMessage); - var payload = new Uint8Array(s.length); - for (var i = 0; i < payload.length; i++) { - payload[i] = s.charCodeAt(i); - } - return this.wrappedProtocol.parseMessages(payload.buffer); - }; - Base64EncodedHubProtocol.prototype.writeMessage = function (message) { - var payload = new Uint8Array(this.wrappedProtocol.writeMessage(message)); - var s = ""; - for (var i = 0; i < payload.byteLength; i++) { - s += String.fromCharCode(payload[i]); - } - // atob/btoa are browsers APIs but they can be polyfilled. If this becomes problematic we can use - // base64-js module - var encodedMessage = btoa(s); - return encodedMessage.length.toString() + ":" + encodedMessage + ";"; - }; - return Base64EncodedHubProtocol; + Subscription.prototype.dispose = function () { + var index = this.subject.observers.indexOf(this.observer); + if (index > -1) { + this.subject.observers.splice(index, 1); + } + if (this.subject.observers.length === 0) { + this.subject.cancelCallback().catch(function (_) { }); + } + }; + return Subscription; +}()); +exports.Subscription = Subscription; +var Subject = /** @class */ (function () { + function Subject(cancelCallback) { + this.observers = []; + this.cancelCallback = cancelCallback; + } + Subject.prototype.next = function (item) { + for (var _i = 0, _a = this.observers; _i < _a.length; _i++) { + var observer = _a[_i]; + observer.next(item); + } + }; + Subject.prototype.error = function (err) { + for (var _i = 0, _a = this.observers; _i < _a.length; _i++) { + var observer = _a[_i]; + if (observer.error) { + observer.error(err); + } + } + }; + Subject.prototype.complete = function () { + for (var _i = 0, _a = this.observers; _i < _a.length; _i++) { + var observer = _a[_i]; + if (observer.complete) { + observer.complete(); + } + } + }; + Subject.prototype.subscribe = function (observer) { + this.observers.push(observer); + return new Subscription(this, observer); + }; + return Subject; }()); -exports.Base64EncodedHubProtocol = Base64EncodedHubProtocol; +exports.Subject = Subject; }); -unwrapExports(Base64EncodedHubProtocol_1); -var Base64EncodedHubProtocol_2 = Base64EncodedHubProtocol_1.Base64EncodedHubProtocol; +unwrapExports(Observable); +var Observable_1 = Observable.Subscription; +var Observable_2 = Observable.Subject; var HubConnection_1 = createCommonjsModule(function (module, exports) { var __awaiter = (commonjsGlobal && commonjsGlobal.__awaiter) || function (thisArg, _arguments, P, generator) { @@ -2242,7 +2438,6 @@ exports.JsonHubProtocol = JsonHubProtocol_1.JsonHubProtocol; - var DEFAULT_TIMEOUT_IN_MS = 30 * 1000; var HubConnection = /** @class */ (function () { function HubConnection(urlOrConnection, options) { @@ -2250,6 +2445,7 @@ var HubConnection = /** @class */ (function () { var _this = this; options = options || {}; this.timeoutInMilliseconds = options.timeoutInMilliseconds || DEFAULT_TIMEOUT_IN_MS; + this.protocol = options.protocol || new JsonHubProtocol_1.JsonHubProtocol(); if (typeof urlOrConnection === "string") { this.connection = new HttpConnection_1.HttpConnection(urlOrConnection, options); } @@ -2257,46 +2453,107 @@ var HubConnection = /** @class */ (function () { this.connection = urlOrConnection; } this.logger = Loggers.LoggerFactory.createLogger(options.logger); - this.protocol = options.protocol || new JsonHubProtocol_1.JsonHubProtocol(); this.connection.onreceive = function (data) { return _this.processIncomingData(data); }; this.connection.onclose = function (error) { return _this.connectionClosed(error); }; - this.callbacks = new Map(); - this.methods = new Map(); + this.callbacks = {}; + this.methods = {}; this.closedCallbacks = []; this.id = 0; } HubConnection.prototype.processIncomingData = function (data) { - if (this.timeoutHandle !== undefined) { - clearTimeout(this.timeoutHandle); + this.cleanupTimeout(); + if (!this.receivedHandshakeResponse) { + data = this.processHandshakeResponse(data); + this.receivedHandshakeResponse = true; } - // Parse the messages - var messages = this.protocol.parseMessages(data); - for (var i = 0; i < messages.length; ++i) { - var message = messages[i]; - switch (message.type) { - case 1 /* Invocation */: - this.invokeClientMethod(message); - break; - case 2 /* StreamItem */: - case 3 /* Completion */: - var callback = this.callbacks.get(message.invocationId); - if (callback != null) { - if (message.type === 3 /* Completion */) { - this.callbacks.delete(message.invocationId); + // Data may have all been read when processing handshake response + if (data) { + // Parse the messages + var messages = this.protocol.parseMessages(data, this.logger); + for (var _i = 0, messages_1 = messages; _i < messages_1.length; _i++) { + var message = messages_1[_i]; + switch (message.type) { + case 1 /* Invocation */: + this.invokeClientMethod(message); + break; + case 2 /* StreamItem */: + case 3 /* Completion */: + var callback = this.callbacks[message.invocationId]; + if (callback != null) { + if (message.type === 3 /* Completion */) { + delete this.callbacks[message.invocationId]; + } + callback(message); } - callback(message); - } - break; - case 6 /* Ping */: - // Don't care about pings - break; - default: - this.logger.log(ILogger.LogLevel.Warning, "Invalid message type: " + data); - break; + break; + case 6 /* Ping */: + // Don't care about pings + break; + case 7 /* Close */: + this.logger.log(ILogger.LogLevel.Information, "Close message received from server."); + this.connection.stop(message.error ? new Error("Server returned an error on close: " + message.error) : null); + break; + default: + this.logger.log(ILogger.LogLevel.Warning, "Invalid message type: " + message.type); + break; + } } } this.configureTimeout(); }; + HubConnection.prototype.processHandshakeResponse = function (data) { + var responseMessage; + var messageData; + var remainingData; + try { + if (data instanceof ArrayBuffer) { + // Format is binary but still need to read JSON text from handshake response + var binaryData = new Uint8Array(data); + var separatorIndex = binaryData.indexOf(TextMessageFormat_1.TextMessageFormat.RecordSeparatorCode); + if (separatorIndex === -1) { + throw new Error("Message is incomplete."); + } + // content before separator is handshake response + // optional content after is additional messages + var responseLength = separatorIndex + 1; + messageData = String.fromCharCode.apply(null, binaryData.slice(0, responseLength)); + remainingData = (binaryData.byteLength > responseLength) ? binaryData.slice(responseLength).buffer : null; + } + else { + var textData = data; + var separatorIndex = textData.indexOf(TextMessageFormat_1.TextMessageFormat.RecordSeparator); + if (separatorIndex === -1) { + throw new Error("Message is incomplete."); + } + // content before separator is handshake response + // optional content after is additional messages + var responseLength = separatorIndex + 1; + messageData = textData.substring(0, responseLength); + remainingData = (textData.length > responseLength) ? textData.substring(responseLength) : null; + } + // At this point we should have just the single handshake message + var messages = TextMessageFormat_1.TextMessageFormat.parse(messageData); + responseMessage = JSON.parse(messages[0]); + } + catch (e) { + var message = "Error parsing handshake response: " + e; + this.logger.log(ILogger.LogLevel.Error, message); + var error = new Error(message); + this.connection.stop(error); + throw error; + } + if (responseMessage.error) { + var message = "Server returned handshake error: " + responseMessage.error; + this.logger.log(ILogger.LogLevel.Error, message); + this.connection.stop(new Error(message)); + } + else { + this.logger.log(ILogger.LogLevel.Trace, "Server handshake complete."); + } + // multiple messages could have arrived with handshake + // return additional data to be parsed as usual, or null if all parsed + return remainingData; + }; HubConnection.prototype.configureTimeout = function () { var _this = this; if (!this.connection.features || !this.connection.features.inherentKeepAlive) { @@ -2311,7 +2568,7 @@ var HubConnection = /** @class */ (function () { }; HubConnection.prototype.invokeClientMethod = function (invocationMessage) { var _this = this; - var methods = this.methods.get(invocationMessage.target.toLowerCase()); + var methods = this.methods[invocationMessage.target.toLowerCase()]; if (methods) { methods.forEach(function (m) { return m.apply(_this, invocationMessage.arguments); }); if (invocationMessage.invocationId) { @@ -2327,34 +2584,35 @@ var HubConnection = /** @class */ (function () { }; HubConnection.prototype.connectionClosed = function (error) { var _this = this; - this.callbacks.forEach(function (callback) { + var callbacks = this.callbacks; + this.callbacks = {}; + Object.keys(callbacks) + .forEach(function (key) { + var callback = callbacks[key]; callback(undefined, error ? error : new Error("Invocation canceled due to connection being closed.")); }); - this.callbacks.clear(); - this.closedCallbacks.forEach(function (c) { return c.apply(_this, [error]); }); this.cleanupTimeout(); + this.closedCallbacks.forEach(function (c) { return c.apply(_this, [error]); }); }; HubConnection.prototype.start = function () { return __awaiter(this, void 0, void 0, function () { - var requestedTransferMode, actualTransferMode; return __generator(this, function (_a) { switch (_a.label) { case 0: - requestedTransferMode = (this.protocol.type === 2 /* Binary */) - ? 2 /* Binary */ - : 1 /* Text */; - this.connection.features.transferMode = requestedTransferMode; - return [4 /*yield*/, this.connection.start()]; + this.logger.log(ILogger.LogLevel.Trace, "Starting HubConnection."); + this.receivedHandshakeResponse = false; + return [4 /*yield*/, this.connection.start(this.protocol.transferFormat)]; case 1: _a.sent(); - actualTransferMode = this.connection.features.transferMode; - return [4 /*yield*/, this.connection.send(TextMessageFormat_1.TextMessageFormat.write(JSON.stringify({ protocol: this.protocol.name })))]; + this.logger.log(ILogger.LogLevel.Trace, "Sending handshake request."); + // Handshake request is always JSON + return [4 /*yield*/, this.connection.send(TextMessageFormat_1.TextMessageFormat.write(JSON.stringify({ protocol: this.protocol.name, version: this.protocol.version })))]; case 2: + // Handshake request is always JSON _a.sent(); this.logger.log(ILogger.LogLevel.Information, "Using HubProtocol '" + this.protocol.name + "'."); - if (requestedTransferMode === 2 /* Binary */ && actualTransferMode === 1 /* Text */) { - this.protocol = new Base64EncodedHubProtocol_1.Base64EncodedHubProtocol(this.protocol); - } + // defensively cleanup timeout in case we receive a message from the server before we finish start + this.cleanupTimeout(); this.configureTimeout(); return [2 /*return*/]; } @@ -2362,6 +2620,7 @@ var HubConnection = /** @class */ (function () { }); }; HubConnection.prototype.stop = function () { + this.logger.log(ILogger.LogLevel.Trace, "Stopping HubConnection."); this.cleanupTimeout(); return this.connection.stop(); }; @@ -2374,33 +2633,32 @@ var HubConnection = /** @class */ (function () { var invocationDescriptor = this.createStreamInvocation(methodName, args); var subject = new Observable.Subject(function () { var cancelInvocation = _this.createCancelInvocation(invocationDescriptor.invocationId); - var message = _this.protocol.writeMessage(cancelInvocation); - _this.callbacks.delete(invocationDescriptor.invocationId); - return _this.connection.send(message); + var cancelMessage = _this.protocol.writeMessage(cancelInvocation); + delete _this.callbacks[invocationDescriptor.invocationId]; + return _this.connection.send(cancelMessage); }); - this.callbacks.set(invocationDescriptor.invocationId, function (invocationEvent, error) { + this.callbacks[invocationDescriptor.invocationId] = function (invocationEvent, error) { if (error) { subject.error(error); return; } if (invocationEvent.type === 3 /* Completion */) { - var completionMessage = invocationEvent; - if (completionMessage.error) { - subject.error(new Error(completionMessage.error)); + if (invocationEvent.error) { + subject.error(new Error(invocationEvent.error)); } else { subject.complete(); } } else { - subject.next(invocationEvent.item); + subject.next((invocationEvent.item)); } - }); + }; var message = this.protocol.writeMessage(invocationDescriptor); this.connection.send(message) .catch(function (e) { subject.error(e); - _this.callbacks.delete(invocationDescriptor.invocationId); + delete _this.callbacks[invocationDescriptor.invocationId]; }); return subject; }; @@ -2421,7 +2679,7 @@ var HubConnection = /** @class */ (function () { } var invocationDescriptor = this.createInvocation(methodName, args, false); var p = new Promise(function (resolve, reject) { - _this.callbacks.set(invocationDescriptor.invocationId, function (invocationEvent, error) { + _this.callbacks[invocationDescriptor.invocationId] = function (invocationEvent, error) { if (error) { reject(error); return; @@ -2438,38 +2696,50 @@ var HubConnection = /** @class */ (function () { else { reject(new Error("Unexpected message type: " + invocationEvent.type)); } - }); + }; var message = _this.protocol.writeMessage(invocationDescriptor); _this.connection.send(message) .catch(function (e) { reject(e); - _this.callbacks.delete(invocationDescriptor.invocationId); + delete _this.callbacks[invocationDescriptor.invocationId]; }); }); return p; }; - HubConnection.prototype.on = function (methodName, method) { - if (!methodName || !method) { + HubConnection.prototype.on = function (methodName, newMethod) { + if (!methodName || !newMethod) { return; } methodName = methodName.toLowerCase(); - if (!this.methods.has(methodName)) { - this.methods.set(methodName, []); + if (!this.methods[methodName]) { + this.methods[methodName] = []; } - this.methods.get(methodName).push(method); + // Preventing adding the same handler multiple times. + if (this.methods[methodName].indexOf(newMethod) !== -1) { + return; + } + this.methods[methodName].push(newMethod); }; HubConnection.prototype.off = function (methodName, method) { - if (!methodName || !method) { + if (!methodName) { return; } methodName = methodName.toLowerCase(); - var handlers = this.methods.get(methodName); + var handlers = this.methods[methodName]; if (!handlers) { return; } - var removeIdx = handlers.indexOf(method); - if (removeIdx != -1) { - handlers.splice(removeIdx, 1); + if (method) { + var removeIdx = handlers.indexOf(method); + if (removeIdx !== -1) { + handlers.splice(removeIdx, 1); + if (handlers.length === 0) { + delete this.methods[methodName]; + } + } + } + else { + delete this.methods[methodName]; } }; HubConnection.prototype.onclose = function (callback) { @@ -2485,19 +2755,19 @@ var HubConnection = /** @class */ (function () { HubConnection.prototype.createInvocation = function (methodName, args, nonblocking) { if (nonblocking) { return { - type: 1 /* Invocation */, - target: methodName, arguments: args, + target: methodName, + type: 1 /* Invocation */, }; } else { var id = this.id; this.id++; return { - type: 1 /* Invocation */, + arguments: args, invocationId: id.toString(), target: methodName, - arguments: args, + type: 1 /* Invocation */, }; } }; @@ -2505,16 +2775,16 @@ var HubConnection = /** @class */ (function () { var id = this.id; this.id++; return { - type: 4 /* StreamInvocation */, + arguments: args, invocationId: id.toString(), target: methodName, - arguments: args, + type: 4 /* StreamInvocation */, }; }; HubConnection.prototype.createCancelInvocation = function (id) { return { - type: 5 /* CancelInvocation */, invocationId: id, + type: 5 /* CancelInvocation */, }; }; return HubConnection; diff --git a/ASP.NET Core Demo/WebApplication.React/ViewModels/SimpleListVM.cs b/ASP.NET Core Demo/WebApplication.React/ViewModels/SimpleListVM.cs index edffb8a1..50d91517 100644 --- a/ASP.NET Core Demo/WebApplication.React/ViewModels/SimpleListVM.cs +++ b/ASP.NET Core Demo/WebApplication.React/ViewModels/SimpleListVM.cs @@ -7,14 +7,14 @@ namespace ViewModels { /// - /// This view model demonstrates simple CRUD operation on a list. + /// This view model demonstrates simple CRUD operation on a list. /// public class SimpleListVM : BaseVM { private readonly EmployeeService _employeeService; /// - /// The class that holds employee info to send to the browser. + /// The class that holds employee info to send to the browser. /// public class EmployeeInfo { @@ -48,7 +48,7 @@ public SimpleListVM() AddProperty("Employees_itemKey").SubscribeTo(Observable.Return(nameof(EmployeeInfo.Id))); // When the Add button is clicked, this property will receive the new employee full name input. - this.AddProperty("Add").Skip(1).Subscribe(fullName => + this.AddProperty("Add").Subscribe(fullName => { var names = fullName.Split(new char[] { ' ' }, 2); var newRecord = new EmployeeModel @@ -69,7 +69,7 @@ public SimpleListVM() var showNotification = AddProperty("ShowNotification"); // When a list item is edited, this property will receive the edited item. - AddProperty("Update").Skip(1).Subscribe(changes => + AddProperty("Update").Subscribe(changes => { /// Real world app would do database update operation here. var record = _employeeService.GetById(changes.Id); @@ -84,7 +84,7 @@ public SimpleListVM() }); // When the Remove button is clicked, this property will receive the employee Id to remove. - AddProperty("Remove").Skip(1).Subscribe(id => + AddProperty("Remove").Subscribe(id => { _employeeService.Delete(id); diff --git a/ASP.NET Core Demo/WebApplication.React/web.config b/ASP.NET Core Demo/WebApplication.React/web.config index dc0514fc..8700b60c 100644 --- a/ASP.NET Core Demo/WebApplication.React/web.config +++ b/ASP.NET Core Demo/WebApplication.React/web.config @@ -1,14 +1,12 @@  - - - + - + - + \ No newline at end of file diff --git a/ASP.NET Core Demo/WebApplication.React/wwwroot/lib/bundle.min.js b/ASP.NET Core Demo/WebApplication.React/wwwroot/lib/bundle.min.js index 30b7801e..d903a9bb 100644 --- a/ASP.NET Core Demo/WebApplication.React/wwwroot/lib/bundle.min.js +++ b/ASP.NET Core Demo/WebApplication.React/wwwroot/lib/bundle.min.js @@ -9381,7 +9381,7 @@ if (true) /* WEBPACK VAR INJECTION */(function(global, process) {/* @license * Copyright (c) .NET Foundation. All rights reserved. * Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -*/ + */ (function (global, factory) { true ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : @@ -10610,8 +10610,14 @@ if (true) var HttpError = /** @class */ (function (_super) { __extends(HttpError, _super); function HttpError(errorMessage, statusCode) { - var _this = _super.call(this, errorMessage) || this; + var _newTarget = this.constructor; + var _this = this; + var trueProto = _newTarget.prototype; + _this = _super.call(this, errorMessage) || this; _this.statusCode = statusCode; + // Workaround issue in Typescript compiler + // https://github.com/Microsoft/TypeScript/issues/13965#issuecomment-278570200 + _this.__proto__ = trueProto; return _this; } return HttpError; @@ -10620,8 +10626,15 @@ if (true) var TimeoutError = /** @class */ (function (_super) { __extends(TimeoutError, _super); function TimeoutError(errorMessage) { + var _newTarget = this.constructor; if (errorMessage === void 0) { errorMessage = "A timeout occurred."; } - return _super.call(this, errorMessage) || this; + var _this = this; + var trueProto = _newTarget.prototype; + _this = _super.call(this, errorMessage) || this; + // Workaround issue in Typescript compiler + // https://github.com/Microsoft/TypeScript/issues/13965#issuecomment-278570200 + _this.__proto__ = trueProto; + return _this; } return TimeoutError; }(Error)); @@ -10633,6 +10646,22 @@ if (true) var Errors_1 = Errors.HttpError; var Errors_2 = Errors.TimeoutError; + var ILogger = createCommonjsModule(function (module, exports) { + Object.defineProperty(exports, "__esModule", { value: true }); + var LogLevel; + (function (LogLevel) { + LogLevel[LogLevel["Trace"] = 0] = "Trace"; + LogLevel[LogLevel["Information"] = 1] = "Information"; + LogLevel[LogLevel["Warning"] = 2] = "Warning"; + LogLevel[LogLevel["Error"] = 3] = "Error"; + LogLevel[LogLevel["None"] = 4] = "None"; + })(LogLevel = exports.LogLevel || (exports.LogLevel = {})); + + }); + + unwrapExports(ILogger); + var ILogger_1 = ILogger.LogLevel; + var HttpClient_1 = createCommonjsModule(function (module, exports) { var __extends = (commonjsGlobal && commonjsGlobal.__extends) || (function () { var extendStatics = Object.setPrototypeOf || @@ -10654,6 +10683,7 @@ if (true) }; Object.defineProperty(exports, "__esModule", { value: true }); + var HttpResponse = /** @class */ (function () { function HttpResponse(statusCode, statusText, content) { this.statusCode = statusCode; @@ -10677,16 +10707,21 @@ if (true) exports.HttpClient = HttpClient; var DefaultHttpClient = /** @class */ (function (_super) { __extends(DefaultHttpClient, _super); - function DefaultHttpClient() { - return _super !== null && _super.apply(this, arguments) || this; + function DefaultHttpClient(logger) { + var _this = _super.call(this) || this; + _this.logger = logger; + return _this; } DefaultHttpClient.prototype.send = function (request) { + var _this = this; return new Promise(function (resolve, reject) { var xhr = new XMLHttpRequest(); xhr.open(request.method, request.url, true); + xhr.withCredentials = true; xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest"); if (request.headers) { - request.headers.forEach(function (value, header) { return xhr.setRequestHeader(header, value); }); + Object.keys(request.headers) + .forEach(function (header) { return xhr.setRequestHeader(header, request.headers[header]); }); } if (request.responseType) { xhr.responseType = request.responseType; @@ -10711,9 +10746,11 @@ if (true) } }; xhr.onerror = function () { + _this.logger.log(ILogger.LogLevel.Warning, "Error from HTTP request. " + xhr.status + ": " + xhr.statusText); reject(new Errors.HttpError(xhr.statusText, xhr.status)); }; xhr.ontimeout = function () { + _this.logger.log(ILogger.LogLevel.Warning, "Timeout from HTTP request."); reject(new Errors.TimeoutError()); }; xhr.send(request.content || ""); @@ -10730,21 +10767,67 @@ if (true) var HttpClient_3 = HttpClient_1.HttpClient; var HttpClient_4 = HttpClient_1.DefaultHttpClient; - var ILogger = createCommonjsModule(function (module, exports) { + var Loggers = createCommonjsModule(function (module, exports) { Object.defineProperty(exports, "__esModule", { value: true }); - var LogLevel; - (function (LogLevel) { - LogLevel[LogLevel["Trace"] = 0] = "Trace"; - LogLevel[LogLevel["Information"] = 1] = "Information"; - LogLevel[LogLevel["Warning"] = 2] = "Warning"; - LogLevel[LogLevel["Error"] = 3] = "Error"; - LogLevel[LogLevel["None"] = 4] = "None"; - })(LogLevel = exports.LogLevel || (exports.LogLevel = {})); + + var NullLogger = /** @class */ (function () { + function NullLogger() { + } + NullLogger.prototype.log = function (logLevel, message) { + }; + return NullLogger; + }()); + exports.NullLogger = NullLogger; + var ConsoleLogger = /** @class */ (function () { + function ConsoleLogger(minimumLogLevel) { + this.minimumLogLevel = minimumLogLevel; + } + ConsoleLogger.prototype.log = function (logLevel, message) { + if (logLevel >= this.minimumLogLevel) { + switch (logLevel) { + case ILogger.LogLevel.Error: + console.error(ILogger.LogLevel[logLevel] + ": " + message); + break; + case ILogger.LogLevel.Warning: + console.warn(ILogger.LogLevel[logLevel] + ": " + message); + break; + case ILogger.LogLevel.Information: + console.info(ILogger.LogLevel[logLevel] + ": " + message); + break; + default: + console.log(ILogger.LogLevel[logLevel] + ": " + message); + break; + } + } + }; + return ConsoleLogger; + }()); + exports.ConsoleLogger = ConsoleLogger; + var LoggerFactory = /** @class */ (function () { + function LoggerFactory() { + } + LoggerFactory.createLogger = function (logging) { + if (logging === undefined) { + return new ConsoleLogger(ILogger.LogLevel.Information); + } + if (logging === null) { + return new NullLogger(); + } + if (logging.log) { + return logging; + } + return new ConsoleLogger(logging); + }; + return LoggerFactory; + }()); + exports.LoggerFactory = LoggerFactory; }); - unwrapExports(ILogger); - var ILogger_1 = ILogger.LogLevel; + unwrapExports(Loggers); + var Loggers_1 = Loggers.NullLogger; + var Loggers_2 = Loggers.ConsoleLogger; + var Loggers_3 = Loggers.LoggerFactory; var AbortController_1 = createCommonjsModule(function (module, exports) { Object.defineProperty(exports, "__esModule", { value: true }); @@ -10786,6 +10869,31 @@ if (true) unwrapExports(AbortController_1); var AbortController_2 = AbortController_1.AbortController; + var Utils = createCommonjsModule(function (module, exports) { + Object.defineProperty(exports, "__esModule", { value: true }); + var Arg = /** @class */ (function () { + function Arg() { + } + Arg.isRequired = function (val, name) { + if (val === null || val === undefined) { + throw new Error("The '" + name + "' argument is required."); + } + }; + Arg.isIn = function (val, values, name) { + // TypeScript enums have keys for **both** the name and the value of each enum member on the type itself. + if (!(val in values)) { + throw new Error("Unknown " + name + " value: " + val + "."); + } + }; + return Arg; + }()); + exports.Arg = Arg; + + }); + + unwrapExports(Utils); + var Utils_1 = Utils.Arg; + var Transports = createCommonjsModule(function (module, exports) { var __awaiter = (commonjsGlobal && commonjsGlobal.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { @@ -10826,19 +10934,33 @@ if (true) + var TransportType; (function (TransportType) { TransportType[TransportType["WebSockets"] = 0] = "WebSockets"; TransportType[TransportType["ServerSentEvents"] = 1] = "ServerSentEvents"; TransportType[TransportType["LongPolling"] = 2] = "LongPolling"; })(TransportType = exports.TransportType || (exports.TransportType = {})); + var TransferFormat; + (function (TransferFormat) { + TransferFormat[TransferFormat["Text"] = 1] = "Text"; + TransferFormat[TransferFormat["Binary"] = 2] = "Binary"; + })(TransferFormat = exports.TransferFormat || (exports.TransferFormat = {})); var WebSocketTransport = /** @class */ (function () { function WebSocketTransport(accessTokenFactory, logger) { this.logger = logger; this.accessTokenFactory = accessTokenFactory || (function () { return null; }); } - WebSocketTransport.prototype.connect = function (url, requestedTransferMode, connection) { + WebSocketTransport.prototype.connect = function (url, transferFormat, connection) { var _this = this; + Utils.Arg.isRequired(url, "url"); + Utils.Arg.isRequired(transferFormat, "transferFormat"); + Utils.Arg.isIn(transferFormat, TransferFormat, "transferFormat"); + Utils.Arg.isRequired(connection, "connection"); + if (typeof (WebSocket) === "undefined") { + throw new Error("'WebSocket' is not supported in your environment."); + } + this.logger.log(ILogger.LogLevel.Trace, "(WebSockets transport) Connecting"); return new Promise(function (resolve, reject) { url = url.replace(/^http/, "ws"); var token = _this.accessTokenFactory(); @@ -10846,19 +10968,19 @@ if (true) url += (url.indexOf("?") < 0 ? "?" : "&") + ("access_token=" + encodeURIComponent(token)); } var webSocket = new WebSocket(url); - if (requestedTransferMode == 2 /* Binary */) { + if (transferFormat === TransferFormat.Binary) { webSocket.binaryType = "arraybuffer"; } webSocket.onopen = function (event) { _this.logger.log(ILogger.LogLevel.Information, "WebSocket connected to " + url); _this.webSocket = webSocket; - resolve(requestedTransferMode); + resolve(); }; webSocket.onerror = function (event) { - reject(); + reject(event.error); }; webSocket.onmessage = function (message) { - _this.logger.log(ILogger.LogLevel.Trace, "(WebSockets transport) data received: " + message.data); + _this.logger.log(ILogger.LogLevel.Trace, "(WebSockets transport) data received. " + getDataDetail(message.data) + "."); if (_this.onreceive) { _this.onreceive(message.data); } @@ -10878,6 +11000,7 @@ if (true) }; WebSocketTransport.prototype.send = function (data) { if (this.webSocket && this.webSocket.readyState === WebSocket.OPEN) { + this.logger.log(ILogger.LogLevel.Trace, "(WebSockets transport) sending data. " + getDataDetail(data) + "."); this.webSocket.send(data); return Promise.resolve(); } @@ -10899,23 +11022,31 @@ if (true) this.accessTokenFactory = accessTokenFactory || (function () { return null; }); this.logger = logger; } - ServerSentEventsTransport.prototype.connect = function (url, requestedTransferMode, connection) { + ServerSentEventsTransport.prototype.connect = function (url, transferFormat, connection) { var _this = this; + Utils.Arg.isRequired(url, "url"); + Utils.Arg.isRequired(transferFormat, "transferFormat"); + Utils.Arg.isIn(transferFormat, TransferFormat, "transferFormat"); + Utils.Arg.isRequired(connection, "connection"); if (typeof (EventSource) === "undefined") { - Promise.reject("EventSource not supported by the browser."); + throw new Error("'EventSource' is not supported in your environment."); } + this.logger.log(ILogger.LogLevel.Trace, "(SSE transport) Connecting"); this.url = url; return new Promise(function (resolve, reject) { + if (transferFormat !== TransferFormat.Text) { + reject(new Error("The Server-Sent Events transport only supports the 'Text' transfer format")); + } var token = _this.accessTokenFactory(); if (token) { url += (url.indexOf("?") < 0 ? "?" : "&") + ("access_token=" + encodeURIComponent(token)); } - var eventSource = new EventSource(url); + var eventSource = new EventSource(url, { withCredentials: true }); try { eventSource.onmessage = function (e) { if (_this.onreceive) { try { - _this.logger.log(ILogger.LogLevel.Trace, "(SSE transport) data received: " + e.data); + _this.logger.log(ILogger.LogLevel.Trace, "(SSE transport) data received. " + getDataDetail(e.data) + "."); _this.onreceive(e.data); } catch (error) { @@ -10927,7 +11058,7 @@ if (true) } }; eventSource.onerror = function (e) { - reject(); + reject(new Error(e.message || "Error occurred")); // don't report an error if the transport did not start successfully if (_this.eventSource && _this.onclose) { _this.onclose(new Error(e.message || "Error occurred")); @@ -10937,7 +11068,7 @@ if (true) _this.logger.log(ILogger.LogLevel.Information, "SSE connected to " + _this.url); _this.eventSource = eventSource; // SSE is a text protocol - resolve(1 /* Text */); + resolve(); }; } catch (e) { @@ -10948,7 +11079,7 @@ if (true) ServerSentEventsTransport.prototype.send = function (data) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { - return [2 /*return*/, send(this.httpClient, this.url, this.accessTokenFactory, data)]; + return [2 /*return*/, send(this.logger, "SSE", this.httpClient, this.url, this.accessTokenFactory, data)]; }); }); }; @@ -10969,34 +11100,40 @@ if (true) this.logger = logger; this.pollAbort = new AbortController_1.AbortController(); } - LongPollingTransport.prototype.connect = function (url, requestedTransferMode, connection) { + LongPollingTransport.prototype.connect = function (url, transferFormat, connection) { + Utils.Arg.isRequired(url, "url"); + Utils.Arg.isRequired(transferFormat, "transferFormat"); + Utils.Arg.isIn(transferFormat, TransferFormat, "transferFormat"); + Utils.Arg.isRequired(connection, "connection"); this.url = url; + this.logger.log(ILogger.LogLevel.Trace, "(LongPolling transport) Connecting"); // Set a flag indicating we have inherent keep-alive in this transport. connection.features.inherentKeepAlive = true; - if (requestedTransferMode === 2 /* Binary */ && (typeof new XMLHttpRequest().responseType !== "string")) { + if (transferFormat === TransferFormat.Binary && (typeof new XMLHttpRequest().responseType !== "string")) { // This will work if we fix: https://github.com/aspnet/SignalR/issues/742 throw new Error("Binary protocols over XmlHttpRequest not implementing advanced features are not supported."); } - this.poll(this.url, requestedTransferMode); - return Promise.resolve(requestedTransferMode); + this.poll(this.url, transferFormat); + return Promise.resolve(); }; - LongPollingTransport.prototype.poll = function (url, transferMode) { + LongPollingTransport.prototype.poll = function (url, transferFormat) { return __awaiter(this, void 0, void 0, function () { var pollOptions, token, pollUrl, response, e_1; return __generator(this, function (_a) { switch (_a.label) { case 0: pollOptions = { - timeout: 120000, abortSignal: this.pollAbort.signal, - headers: new Map(), + headers: {}, + timeout: 90000, }; - if (transferMode === 2 /* Binary */) { + if (transferFormat === TransferFormat.Binary) { pollOptions.responseType = "arraybuffer"; } token = this.accessTokenFactory(); if (token) { - pollOptions.headers.set("Authorization", "Bearer " + token); + // tslint:disable-next-line:no-string-literal + pollOptions.headers["Authorization"] = "Bearer " + token; } _a.label = 1; case 1: @@ -11028,7 +11165,7 @@ if (true) else { // Process the response if (response.content) { - this.logger.log(ILogger.LogLevel.Trace, "(LongPolling transport) data received: " + response.content); + this.logger.log(ILogger.LogLevel.Trace, "(LongPolling transport) data received. " + getDataDetail(response.content) + "."); if (this.onreceive) { this.onreceive(response.content); } @@ -11062,7 +11199,7 @@ if (true) LongPollingTransport.prototype.send = function (data) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { - return [2 /*return*/, send(this.httpClient, this.url, this.accessTokenFactory, data)]; + return [2 /*return*/, send(this.logger, "LongPolling", this.httpClient, this.url, this.accessTokenFactory, data)]; }); }); }; @@ -11073,23 +11210,34 @@ if (true) return LongPollingTransport; }()); exports.LongPollingTransport = LongPollingTransport; - function send(httpClient, url, accessTokenFactory, content) { + function getDataDetail(data) { + var length = null; + if (data instanceof ArrayBuffer) { + length = "Binary data of length " + data.byteLength; + } + else if (typeof data === "string") { + length = "String data of length " + data.length; + } + return length; + } + function send(logger, transportName, httpClient, url, accessTokenFactory, content) { return __awaiter(this, void 0, void 0, function () { - var headers, token; - return __generator(this, function (_a) { - switch (_a.label) { + var headers, token, response, _a; + return __generator(this, function (_b) { + switch (_b.label) { case 0: token = accessTokenFactory(); if (token) { - headers = new Map(); - headers.set("Authorization", "Bearer " + accessTokenFactory()); + headers = (_a = {}, _a["Authorization"] = "Bearer " + accessTokenFactory(), _a); } + logger.log(ILogger.LogLevel.Trace, "(" + transportName + " transport) sending data. " + getDataDetail(content) + "."); return [4 /*yield*/, httpClient.post(url, { content: content, - headers: headers + headers: headers, })]; case 1: - _a.sent(); + response = _b.sent(); + logger.log(ILogger.LogLevel.Trace, "(" + transportName + " transport) request complete. Response status: " + response.statusCode + "."); return [2 /*return*/]; } }); @@ -11100,69 +11248,10 @@ if (true) unwrapExports(Transports); var Transports_1 = Transports.TransportType; - var Transports_2 = Transports.WebSocketTransport; - var Transports_3 = Transports.ServerSentEventsTransport; - var Transports_4 = Transports.LongPollingTransport; - - var Loggers = createCommonjsModule(function (module, exports) { - Object.defineProperty(exports, "__esModule", { value: true }); - - var NullLogger = /** @class */ (function () { - function NullLogger() { - } - NullLogger.prototype.log = function (logLevel, message) { - }; - return NullLogger; - }()); - exports.NullLogger = NullLogger; - var ConsoleLogger = /** @class */ (function () { - function ConsoleLogger(minimumLogLevel) { - this.minimumLogLevel = minimumLogLevel; - } - ConsoleLogger.prototype.log = function (logLevel, message) { - if (logLevel >= this.minimumLogLevel) { - switch (logLevel) { - case ILogger.LogLevel.Error: - console.error(ILogger.LogLevel[logLevel] + ": " + message); - break; - case ILogger.LogLevel.Warning: - console.warn(ILogger.LogLevel[logLevel] + ": " + message); - break; - case ILogger.LogLevel.Information: - console.info(ILogger.LogLevel[logLevel] + ": " + message); - break; - default: - console.log(ILogger.LogLevel[logLevel] + ": " + message); - break; - } - } - }; - return ConsoleLogger; - }()); - exports.ConsoleLogger = ConsoleLogger; - var LoggerFactory; - (function (LoggerFactory) { - function createLogger(logging) { - if (logging === undefined) { - return new ConsoleLogger(ILogger.LogLevel.Information); - } - if (logging === null) { - return new NullLogger(); - } - if (logging.log) { - return logging; - } - return new ConsoleLogger(logging); - } - LoggerFactory.createLogger = createLogger; - })(LoggerFactory = exports.LoggerFactory || (exports.LoggerFactory = {})); - - }); - - unwrapExports(Loggers); - var Loggers_1 = Loggers.NullLogger; - var Loggers_2 = Loggers.ConsoleLogger; - var Loggers_3 = Loggers.LoggerFactory; + var Transports_2 = Transports.TransferFormat; + var Transports_3 = Transports.WebSocketTransport; + var Transports_4 = Transports.ServerSentEventsTransport; + var Transports_5 = Transports.LongPollingTransport; var HttpConnection_1 = createCommonjsModule(function (module, exports) { var __awaiter = (commonjsGlobal && commonjsGlobal.__awaiter) || function (thisArg, _arguments, P, generator) { @@ -11205,131 +11294,231 @@ if (true) + var HttpConnection = /** @class */ (function () { function HttpConnection(url, options) { if (options === void 0) { options = {}; } this.features = {}; + Utils.Arg.isRequired(url, "url"); this.logger = Loggers.LoggerFactory.createLogger(options.logger); this.baseUrl = this.resolveUrl(url); options = options || {}; options.accessTokenFactory = options.accessTokenFactory || (function () { return null; }); - this.httpClient = options.httpClient || new HttpClient_1.DefaultHttpClient(); + this.httpClient = options.httpClient || new HttpClient_1.DefaultHttpClient(this.logger); this.connectionState = 2 /* Disconnected */; this.options = options; } - HttpConnection.prototype.start = function () { - return __awaiter(this, void 0, void 0, function () { - return __generator(this, function (_a) { - if (this.connectionState !== 2 /* Disconnected */) { - return [2 /*return*/, Promise.reject(new Error("Cannot start a connection that is not in the 'Disconnected' state."))]; - } - this.connectionState = 0 /* Connecting */; - this.startPromise = this.startInternal(); - return [2 /*return*/, this.startPromise]; - }); - }); + HttpConnection.prototype.start = function (transferFormat) { + Utils.Arg.isRequired(transferFormat, "transferFormat"); + Utils.Arg.isIn(transferFormat, Transports.TransferFormat, "transferFormat"); + this.logger.log(ILogger.LogLevel.Trace, "Starting connection with transfer format '" + Transports.TransferFormat[transferFormat] + "'."); + if (this.connectionState !== 2 /* Disconnected */) { + return Promise.reject(new Error("Cannot start a connection that is not in the 'Disconnected' state.")); + } + this.connectionState = 0 /* Connecting */; + this.startPromise = this.startInternal(transferFormat); + return this.startPromise; }; - HttpConnection.prototype.startInternal = function () { + HttpConnection.prototype.startInternal = function (transferFormat) { return __awaiter(this, void 0, void 0, function () { var _this = this; - var headers, token, negotiatePayload, negotiateResponse, requestedTransferMode, _a, e_1; + var token, headers, negotiateResponse, e_1, _a; return __generator(this, function (_b) { switch (_b.label) { case 0: - _b.trys.push([0, 5, , 6]); - if (!(this.options.transport === Transports.TransportType.WebSockets)) return [3 /*break*/, 1]; + _b.trys.push([0, 6, , 7]); + if (!(this.options.transport === Transports.TransportType.WebSockets)) return [3 /*break*/, 2]; // No need to add a connection ID in this case this.url = this.baseUrl; - this.transport = this.createTransport(this.options.transport, [Transports.TransportType[Transports.TransportType.WebSockets]]); - return [3 /*break*/, 3]; + this.transport = this.constructTransport(Transports.TransportType.WebSockets); + // We should just call connect directly in this case. + // No fallback or negotiate in this case. + return [4 /*yield*/, this.transport.connect(this.url, transferFormat, this)]; case 1: - headers = void 0; + // We should just call connect directly in this case. + // No fallback or negotiate in this case. + _b.sent(); + return [3 /*break*/, 5]; + case 2: token = this.options.accessTokenFactory(); + headers = void 0; if (token) { - headers = new Map(); - headers.set("Authorization", "Bearer " + token); + headers = (_a = {}, _a["Authorization"] = "Bearer " + token, _a); } - return [4 /*yield*/, this.httpClient.post(this.resolveNegotiateUrl(this.baseUrl), { - content: "", - headers: headers - })]; - case 2: - negotiatePayload = _b.sent(); - negotiateResponse = JSON.parse(negotiatePayload.content); - this.connectionId = negotiateResponse.connectionId; + return [4 /*yield*/, this.getNegotiationResponse(headers)]; + case 3: + negotiateResponse = _b.sent(); // the user tries to stop the the connection when it is being started - if (this.connectionState == 2 /* Disconnected */) { + if (this.connectionState === 2 /* Disconnected */) { return [2 /*return*/]; } - if (this.connectionId) { - this.url = this.baseUrl + (this.baseUrl.indexOf("?") === -1 ? "?" : "&") + ("id=" + this.connectionId); - this.transport = this.createTransport(this.options.transport, negotiateResponse.availableTransports); - } - _b.label = 3; - case 3: + return [4 /*yield*/, this.createTransport(this.options.transport, negotiateResponse, transferFormat, headers)]; + case 4: + _b.sent(); + _b.label = 5; + case 5: this.transport.onreceive = this.onreceive; this.transport.onclose = function (e) { return _this.stopConnection(true, e); }; - requestedTransferMode = this.features.transferMode === 2 /* Binary */ - ? 2 /* Binary */ - : 1 /* Text */; - _a = this.features; - return [4 /*yield*/, this.transport.connect(this.url, requestedTransferMode, this)]; - case 4: - _a.transferMode = _b.sent(); // only change the state if we were connecting to not overwrite // the state if the connection is already marked as Disconnected this.changeState(0 /* Connecting */, 1 /* Connected */); - return [3 /*break*/, 6]; - case 5: + return [3 /*break*/, 7]; + case 6: e_1 = _b.sent(); - this.logger.log(ILogger.LogLevel.Error, "Failed to start the connection. " + e_1); + this.logger.log(ILogger.LogLevel.Error, "Failed to start the connection: " + e_1); this.connectionState = 2 /* Disconnected */; this.transport = null; throw e_1; + case 7: return [2 /*return*/]; + } + }); + }); + }; + HttpConnection.prototype.getNegotiationResponse = function (headers) { + return __awaiter(this, void 0, void 0, function () { + var negotiateUrl, response, e_2; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + negotiateUrl = this.resolveNegotiateUrl(this.baseUrl); + this.logger.log(ILogger.LogLevel.Trace, "Sending negotiation request: " + negotiateUrl); + _a.label = 1; + case 1: + _a.trys.push([1, 3, , 4]); + return [4 /*yield*/, this.httpClient.post(negotiateUrl, { + content: "", + headers: headers, + })]; + case 2: + response = _a.sent(); + return [2 /*return*/, JSON.parse(response.content)]; + case 3: + e_2 = _a.sent(); + this.logger.log(ILogger.LogLevel.Error, "Failed to complete negotiation with the server: " + e_2); + throw e_2; + case 4: return [2 /*return*/]; + } + }); + }); + }; + HttpConnection.prototype.updateConnectionId = function (negotiateResponse) { + this.connectionId = negotiateResponse.connectionId; + this.url = this.baseUrl + (this.baseUrl.indexOf("?") === -1 ? "?" : "&") + ("id=" + this.connectionId); + }; + HttpConnection.prototype.createTransport = function (requestedTransport, negotiateResponse, requestedTransferFormat, headers) { + return __awaiter(this, void 0, void 0, function () { + var transports, _i, transports_1, endpoint, transport, ex_1; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + this.updateConnectionId(negotiateResponse); + if (!this.isITransport(requestedTransport)) return [3 /*break*/, 2]; + this.logger.log(ILogger.LogLevel.Trace, "Connection was provided an instance of ITransport, using that directly."); + this.transport = requestedTransport; + return [4 /*yield*/, this.transport.connect(this.url, requestedTransferFormat, this)]; + case 1: + _a.sent(); + // only change the state if we were connecting to not overwrite + // the state if the connection is already marked as Disconnected + this.changeState(0 /* Connecting */, 1 /* Connected */); + return [2 /*return*/]; + case 2: + transports = negotiateResponse.availableTransports; + _i = 0, transports_1 = transports; + _a.label = 3; + case 3: + if (!(_i < transports_1.length)) return [3 /*break*/, 9]; + endpoint = transports_1[_i]; + this.connectionState = 0 /* Connecting */; + transport = this.resolveTransport(endpoint, requestedTransport, requestedTransferFormat); + if (!(typeof transport === "number")) return [3 /*break*/, 8]; + this.transport = this.constructTransport(transport); + if (!(negotiateResponse.connectionId === null)) return [3 /*break*/, 5]; + return [4 /*yield*/, this.getNegotiationResponse(headers)]; + case 4: + negotiateResponse = _a.sent(); + this.updateConnectionId(negotiateResponse); + _a.label = 5; + case 5: + _a.trys.push([5, 7, , 8]); + return [4 /*yield*/, this.transport.connect(this.url, requestedTransferFormat, this)]; case 6: - ; + _a.sent(); + this.changeState(0 /* Connecting */, 1 /* Connected */); return [2 /*return*/]; + case 7: + ex_1 = _a.sent(); + this.logger.log(ILogger.LogLevel.Error, "Failed to start the transport '" + Transports.TransportType[transport] + "': " + ex_1); + this.connectionState = 2 /* Disconnected */; + negotiateResponse.connectionId = null; + return [3 /*break*/, 8]; + case 8: + _i++; + return [3 /*break*/, 3]; + case 9: throw new Error("Unable to initialize any of the available transports."); } }); }); }; - HttpConnection.prototype.createTransport = function (transport, availableTransports) { - if ((transport === null || transport === undefined) && availableTransports.length > 0) { - transport = Transports.TransportType[availableTransports[0]]; - } - if (transport === Transports.TransportType.WebSockets && availableTransports.indexOf(Transports.TransportType[transport]) >= 0) { - return new Transports.WebSocketTransport(this.options.accessTokenFactory, this.logger); - } - if (transport === Transports.TransportType.ServerSentEvents && availableTransports.indexOf(Transports.TransportType[transport]) >= 0) { - return new Transports.ServerSentEventsTransport(this.httpClient, this.options.accessTokenFactory, this.logger); + HttpConnection.prototype.constructTransport = function (transport) { + switch (transport) { + case Transports.TransportType.WebSockets: + return new Transports.WebSocketTransport(this.options.accessTokenFactory, this.logger); + case Transports.TransportType.ServerSentEvents: + return new Transports.ServerSentEventsTransport(this.httpClient, this.options.accessTokenFactory, this.logger); + case Transports.TransportType.LongPolling: + return new Transports.LongPollingTransport(this.httpClient, this.options.accessTokenFactory, this.logger); + default: + throw new Error("Unknown transport: " + transport + "."); } - if (transport === Transports.TransportType.LongPolling && availableTransports.indexOf(Transports.TransportType[transport]) >= 0) { - return new Transports.LongPollingTransport(this.httpClient, this.options.accessTokenFactory, this.logger); + }; + HttpConnection.prototype.resolveTransport = function (endpoint, requestedTransport, requestedTransferFormat) { + var transport = Transports.TransportType[endpoint.transport]; + if (transport === null || transport === undefined) { + this.logger.log(ILogger.LogLevel.Trace, "Skipping transport '" + endpoint.transport + "' because it is not supported by this client."); } - if (this.isITransport(transport)) { - return transport; + else { + var transferFormats = endpoint.transferFormats.map(function (s) { return Transports.TransferFormat[s]; }); + if (!requestedTransport || transport === requestedTransport) { + if (transferFormats.indexOf(requestedTransferFormat) >= 0) { + if ((transport === Transports.TransportType.WebSockets && typeof WebSocket === "undefined") || + (transport === Transports.TransportType.ServerSentEvents && typeof EventSource === "undefined")) { + this.logger.log(ILogger.LogLevel.Trace, "Skipping transport '" + Transports.TransportType[transport] + "' because it is not supported in your environment.'"); + } + else { + this.logger.log(ILogger.LogLevel.Trace, "Selecting transport '" + Transports.TransportType[transport] + "'"); + return transport; + } + } + else { + this.logger.log(ILogger.LogLevel.Trace, "Skipping transport '" + Transports.TransportType[transport] + "' because it does not support the requested transfer format '" + Transports.TransferFormat[requestedTransferFormat] + "'."); + } + } + else { + this.logger.log(ILogger.LogLevel.Trace, "Skipping transport '" + Transports.TransportType[transport] + "' because it was disabled by the client."); + } } - throw new Error("No available transports found."); + return null; }; HttpConnection.prototype.isITransport = function (transport) { return typeof (transport) === "object" && "connect" in transport; }; HttpConnection.prototype.changeState = function (from, to) { - if (this.connectionState == from) { + if (this.connectionState === from) { this.connectionState = to; return true; } return false; }; HttpConnection.prototype.send = function (data) { - if (this.connectionState != 1 /* Connected */) { - throw new Error("Cannot send data if the connection is not in the 'Connected' State"); + if (this.connectionState !== 1 /* Connected */) { + throw new Error("Cannot send data if the connection is not in the 'Connected' State."); } return this.transport.send(data); }; HttpConnection.prototype.stop = function (error) { return __awaiter(this, void 0, void 0, function () { - var previousState, e_2; + var previousState, e_3; return __generator(this, function (_a) { switch (_a.label) { case 0: @@ -11343,10 +11532,10 @@ if (true) _a.sent(); return [3 /*break*/, 4]; case 3: - e_2 = _a.sent(); + e_3 = _a.sent(); return [3 /*break*/, 4]; case 4: - this.stopConnection(/*raiseClosed*/ previousState == 1 /* Connected */, error); + this.stopConnection(/*raiseClosed*/ previousState === 1 /* Connected */, error); return [2 /*return*/]; } }); @@ -11373,7 +11562,7 @@ if (true) if (url.lastIndexOf("https://", 0) === 0 || url.lastIndexOf("http://", 0) === 0) { return url; } - if (typeof window === 'undefined' || !window || !window.document) { + if (typeof window === "undefined" || !window || !window.document) { throw new Error("Cannot resolve '" + url + "'."); } var parser = window.document.createElement("a"); @@ -11381,11 +11570,11 @@ if (true) var baseUrl = (!parser.protocol || parser.protocol === ":") ? window.document.location.protocol + "//" + (parser.host || window.document.location.host) : parser.protocol + "//" + parser.host; - if (!url || url[0] != '/') { - url = '/' + url; + if (!url || url[0] !== "/") { + url = "/" + url; } var normalizedUrl = baseUrl + url; - this.logger.log(ILogger.LogLevel.Information, "Normalizing '" + url + "' to '" + normalizedUrl + "'"); + this.logger.log(ILogger.LogLevel.Information, "Normalizing '" + url + "' to '" + normalizedUrl + "'."); return normalizedUrl; }; HttpConnection.prototype.resolveNegotiateUrl = function (url) { @@ -11407,66 +11596,6 @@ if (true) unwrapExports(HttpConnection_1); var HttpConnection_2 = HttpConnection_1.HttpConnection; - var Observable = createCommonjsModule(function (module, exports) { - Object.defineProperty(exports, "__esModule", { value: true }); - var Subscription = /** @class */ (function () { - function Subscription(subject, observer) { - this.subject = subject; - this.observer = observer; - } - Subscription.prototype.dispose = function () { - var index = this.subject.observers.indexOf(this.observer); - if (index > -1) { - this.subject.observers.splice(index, 1); - } - if (this.subject.observers.length === 0) { - this.subject.cancelCallback().catch(function (_) { }); - } - }; - return Subscription; - }()); - exports.Subscription = Subscription; - var Subject = /** @class */ (function () { - function Subject(cancelCallback) { - this.observers = []; - this.cancelCallback = cancelCallback; - } - Subject.prototype.next = function (item) { - for (var _i = 0, _a = this.observers; _i < _a.length; _i++) { - var observer = _a[_i]; - observer.next(item); - } - }; - Subject.prototype.error = function (err) { - for (var _i = 0, _a = this.observers; _i < _a.length; _i++) { - var observer = _a[_i]; - if (observer.error) { - observer.error(err); - } - } - }; - Subject.prototype.complete = function () { - for (var _i = 0, _a = this.observers; _i < _a.length; _i++) { - var observer = _a[_i]; - if (observer.complete) { - observer.complete(); - } - } - }; - Subject.prototype.subscribe = function (observer) { - this.observers.push(observer); - return new Subscription(this, observer); - }; - return Subject; - }()); - exports.Subject = Subject; - - }); - - unwrapExports(Observable); - var Observable_1 = Observable.Subscription; - var Observable_2 = Observable.Subject; - var TextMessageFormat_1 = createCommonjsModule(function (module, exports) { Object.defineProperty(exports, "__esModule", { value: true }); var TextMessageFormat = /** @class */ (function () { @@ -11476,14 +11605,15 @@ if (true) return "" + output + TextMessageFormat.RecordSeparator; }; TextMessageFormat.parse = function (input) { - if (input[input.length - 1] != TextMessageFormat.RecordSeparator) { + if (input[input.length - 1] !== TextMessageFormat.RecordSeparator) { throw new Error("Message is incomplete."); } var messages = input.split(TextMessageFormat.RecordSeparator); messages.pop(); return messages; }; - TextMessageFormat.RecordSeparator = String.fromCharCode(0x1e); + TextMessageFormat.RecordSeparatorCode = 0x1e; + TextMessageFormat.RecordSeparator = String.fromCharCode(TextMessageFormat.RecordSeparatorCode); return TextMessageFormat; }()); exports.TextMessageFormat = TextMessageFormat; @@ -11496,27 +11626,86 @@ if (true) var JsonHubProtocol_1 = createCommonjsModule(function (module, exports) { Object.defineProperty(exports, "__esModule", { value: true }); + + + exports.JSON_HUB_PROTOCOL_NAME = "json"; var JsonHubProtocol = /** @class */ (function () { function JsonHubProtocol() { this.name = exports.JSON_HUB_PROTOCOL_NAME; - this.type = 1 /* Text */; + this.version = 1; + this.transferFormat = Transports.TransferFormat.Text; } - JsonHubProtocol.prototype.parseMessages = function (input) { + JsonHubProtocol.prototype.parseMessages = function (input, logger) { if (!input) { return []; } + if (logger === null) { + logger = new Loggers.NullLogger(); + } // Parse the messages var messages = TextMessageFormat_1.TextMessageFormat.parse(input); var hubMessages = []; - for (var i = 0; i < messages.length; ++i) { - hubMessages.push(JSON.parse(messages[i])); + for (var _i = 0, messages_1 = messages; _i < messages_1.length; _i++) { + var message = messages_1[_i]; + var parsedMessage = JSON.parse(message); + if (typeof parsedMessage.type !== "number") { + throw new Error("Invalid payload."); + } + switch (parsedMessage.type) { + case 1 /* Invocation */: + this.isInvocationMessage(parsedMessage); + break; + case 2 /* StreamItem */: + this.isStreamItemMessage(parsedMessage); + break; + case 3 /* Completion */: + this.isCompletionMessage(parsedMessage); + break; + case 6 /* Ping */: + // Single value, no need to validate + break; + case 7 /* Close */: + // All optional values, no need to validate + break; + default: + // Future protocol changes can add message types, old clients can ignore them + logger.log(ILogger.LogLevel.Information, "Unknown message type '" + parsedMessage.type + "' ignored."); + continue; + } + hubMessages.push(parsedMessage); } return hubMessages; }; JsonHubProtocol.prototype.writeMessage = function (message) { return TextMessageFormat_1.TextMessageFormat.write(JSON.stringify(message)); }; + JsonHubProtocol.prototype.isInvocationMessage = function (message) { + this.assertNotEmptyString(message.target, "Invalid payload for Invocation message."); + if (message.invocationId !== undefined) { + this.assertNotEmptyString(message.invocationId, "Invalid payload for Invocation message."); + } + }; + JsonHubProtocol.prototype.isStreamItemMessage = function (message) { + this.assertNotEmptyString(message.invocationId, "Invalid payload for StreamItem message."); + if (message.item === undefined) { + throw new Error("Invalid payload for StreamItem message."); + } + }; + JsonHubProtocol.prototype.isCompletionMessage = function (message) { + if (message.result && message.error) { + throw new Error("Invalid payload for Completion message."); + } + if (!message.result && message.error) { + this.assertNotEmptyString(message.error, "Invalid payload for Completion message."); + } + this.assertNotEmptyString(message.invocationId, "Invalid payload for Completion message."); + }; + JsonHubProtocol.prototype.assertNotEmptyString = function (value, errorMessage) { + if (typeof value !== "string" || value === "") { + throw new Error(errorMessage); + } + }; return JsonHubProtocol; }()); exports.JsonHubProtocol = JsonHubProtocol; @@ -11527,58 +11716,65 @@ if (true) var JsonHubProtocol_2 = JsonHubProtocol_1.JSON_HUB_PROTOCOL_NAME; var JsonHubProtocol_3 = JsonHubProtocol_1.JsonHubProtocol; - var Base64EncodedHubProtocol_1 = createCommonjsModule(function (module, exports) { + var Observable = createCommonjsModule(function (module, exports) { Object.defineProperty(exports, "__esModule", { value: true }); - var Base64EncodedHubProtocol = /** @class */ (function () { - function Base64EncodedHubProtocol(protocol) { - this.wrappedProtocol = protocol; - this.name = this.wrappedProtocol.name; - this.type = 1 /* Text */; + var Subscription = /** @class */ (function () { + function Subscription(subject, observer) { + this.subject = subject; + this.observer = observer; } - Base64EncodedHubProtocol.prototype.parseMessages = function (input) { - // The format of the message is `size:message;` - var pos = input.indexOf(":"); - if (pos == -1 || input[input.length - 1] != ';') { - throw new Error("Invalid payload."); + Subscription.prototype.dispose = function () { + var index = this.subject.observers.indexOf(this.observer); + if (index > -1) { + this.subject.observers.splice(index, 1); } - var lenStr = input.substring(0, pos); - if (!/^[0-9]+$/.test(lenStr)) { - throw new Error("Invalid length: '" + lenStr + "'"); + if (this.subject.observers.length === 0) { + this.subject.cancelCallback().catch(function (_) { }); } - var messageSize = parseInt(lenStr, 10); - // 2 accounts for ':' after message size and trailing ';' - if (messageSize != input.length - pos - 2) { - throw new Error("Invalid message size."); + }; + return Subscription; + }()); + exports.Subscription = Subscription; + var Subject = /** @class */ (function () { + function Subject(cancelCallback) { + this.observers = []; + this.cancelCallback = cancelCallback; + } + Subject.prototype.next = function (item) { + for (var _i = 0, _a = this.observers; _i < _a.length; _i++) { + var observer = _a[_i]; + observer.next(item); } - var encodedMessage = input.substring(pos + 1, input.length - 1); - // atob/btoa are browsers APIs but they can be polyfilled. If this becomes problematic we can use - // base64-js module - var s = atob(encodedMessage); - var payload = new Uint8Array(s.length); - for (var i = 0; i < payload.length; i++) { - payload[i] = s.charCodeAt(i); + }; + Subject.prototype.error = function (err) { + for (var _i = 0, _a = this.observers; _i < _a.length; _i++) { + var observer = _a[_i]; + if (observer.error) { + observer.error(err); + } } - return this.wrappedProtocol.parseMessages(payload.buffer); }; - Base64EncodedHubProtocol.prototype.writeMessage = function (message) { - var payload = new Uint8Array(this.wrappedProtocol.writeMessage(message)); - var s = ""; - for (var i = 0; i < payload.byteLength; i++) { - s += String.fromCharCode(payload[i]); + Subject.prototype.complete = function () { + for (var _i = 0, _a = this.observers; _i < _a.length; _i++) { + var observer = _a[_i]; + if (observer.complete) { + observer.complete(); + } } - // atob/btoa are browsers APIs but they can be polyfilled. If this becomes problematic we can use - // base64-js module - var encodedMessage = btoa(s); - return encodedMessage.length.toString() + ":" + encodedMessage + ";"; }; - return Base64EncodedHubProtocol; + Subject.prototype.subscribe = function (observer) { + this.observers.push(observer); + return new Subscription(this, observer); + }; + return Subject; }()); - exports.Base64EncodedHubProtocol = Base64EncodedHubProtocol; + exports.Subject = Subject; }); - unwrapExports(Base64EncodedHubProtocol_1); - var Base64EncodedHubProtocol_2 = Base64EncodedHubProtocol_1.Base64EncodedHubProtocol; + unwrapExports(Observable); + var Observable_1 = Observable.Subscription; + var Observable_2 = Observable.Subject; var HubConnection_1 = createCommonjsModule(function (module, exports) { var __awaiter = (commonjsGlobal && commonjsGlobal.__awaiter) || function (thisArg, _arguments, P, generator) { @@ -11624,7 +11820,6 @@ if (true) - var DEFAULT_TIMEOUT_IN_MS = 30 * 1000; var HubConnection = /** @class */ (function () { function HubConnection(urlOrConnection, options) { @@ -11632,6 +11827,7 @@ if (true) var _this = this; options = options || {}; this.timeoutInMilliseconds = options.timeoutInMilliseconds || DEFAULT_TIMEOUT_IN_MS; + this.protocol = options.protocol || new JsonHubProtocol_1.JsonHubProtocol(); if (typeof urlOrConnection === "string") { this.connection = new HttpConnection_1.HttpConnection(urlOrConnection, options); } @@ -11639,46 +11835,107 @@ if (true) this.connection = urlOrConnection; } this.logger = Loggers.LoggerFactory.createLogger(options.logger); - this.protocol = options.protocol || new JsonHubProtocol_1.JsonHubProtocol(); this.connection.onreceive = function (data) { return _this.processIncomingData(data); }; this.connection.onclose = function (error) { return _this.connectionClosed(error); }; - this.callbacks = new Map(); - this.methods = new Map(); + this.callbacks = {}; + this.methods = {}; this.closedCallbacks = []; this.id = 0; } HubConnection.prototype.processIncomingData = function (data) { - if (this.timeoutHandle !== undefined) { - clearTimeout(this.timeoutHandle); + this.cleanupTimeout(); + if (!this.receivedHandshakeResponse) { + data = this.processHandshakeResponse(data); + this.receivedHandshakeResponse = true; } - // Parse the messages - var messages = this.protocol.parseMessages(data); - for (var i = 0; i < messages.length; ++i) { - var message = messages[i]; - switch (message.type) { - case 1 /* Invocation */: - this.invokeClientMethod(message); - break; - case 2 /* StreamItem */: - case 3 /* Completion */: - var callback = this.callbacks.get(message.invocationId); - if (callback != null) { - if (message.type === 3 /* Completion */) { - this.callbacks.delete(message.invocationId); + // Data may have all been read when processing handshake response + if (data) { + // Parse the messages + var messages = this.protocol.parseMessages(data, this.logger); + for (var _i = 0, messages_1 = messages; _i < messages_1.length; _i++) { + var message = messages_1[_i]; + switch (message.type) { + case 1 /* Invocation */: + this.invokeClientMethod(message); + break; + case 2 /* StreamItem */: + case 3 /* Completion */: + var callback = this.callbacks[message.invocationId]; + if (callback != null) { + if (message.type === 3 /* Completion */) { + delete this.callbacks[message.invocationId]; + } + callback(message); } - callback(message); - } - break; - case 6 /* Ping */: - // Don't care about pings - break; - default: - this.logger.log(ILogger.LogLevel.Warning, "Invalid message type: " + data); - break; + break; + case 6 /* Ping */: + // Don't care about pings + break; + case 7 /* Close */: + this.logger.log(ILogger.LogLevel.Information, "Close message received from server."); + this.connection.stop(message.error ? new Error("Server returned an error on close: " + message.error) : null); + break; + default: + this.logger.log(ILogger.LogLevel.Warning, "Invalid message type: " + message.type); + break; + } } } this.configureTimeout(); }; + HubConnection.prototype.processHandshakeResponse = function (data) { + var responseMessage; + var messageData; + var remainingData; + try { + if (data instanceof ArrayBuffer) { + // Format is binary but still need to read JSON text from handshake response + var binaryData = new Uint8Array(data); + var separatorIndex = binaryData.indexOf(TextMessageFormat_1.TextMessageFormat.RecordSeparatorCode); + if (separatorIndex === -1) { + throw new Error("Message is incomplete."); + } + // content before separator is handshake response + // optional content after is additional messages + var responseLength = separatorIndex + 1; + messageData = String.fromCharCode.apply(null, binaryData.slice(0, responseLength)); + remainingData = (binaryData.byteLength > responseLength) ? binaryData.slice(responseLength).buffer : null; + } + else { + var textData = data; + var separatorIndex = textData.indexOf(TextMessageFormat_1.TextMessageFormat.RecordSeparator); + if (separatorIndex === -1) { + throw new Error("Message is incomplete."); + } + // content before separator is handshake response + // optional content after is additional messages + var responseLength = separatorIndex + 1; + messageData = textData.substring(0, responseLength); + remainingData = (textData.length > responseLength) ? textData.substring(responseLength) : null; + } + // At this point we should have just the single handshake message + var messages = TextMessageFormat_1.TextMessageFormat.parse(messageData); + responseMessage = JSON.parse(messages[0]); + } + catch (e) { + var message = "Error parsing handshake response: " + e; + this.logger.log(ILogger.LogLevel.Error, message); + var error = new Error(message); + this.connection.stop(error); + throw error; + } + if (responseMessage.error) { + var message = "Server returned handshake error: " + responseMessage.error; + this.logger.log(ILogger.LogLevel.Error, message); + this.connection.stop(new Error(message)); + } + else { + this.logger.log(ILogger.LogLevel.Trace, "Server handshake complete."); + } + // multiple messages could have arrived with handshake + // return additional data to be parsed as usual, or null if all parsed + return remainingData; + }; HubConnection.prototype.configureTimeout = function () { var _this = this; if (!this.connection.features || !this.connection.features.inherentKeepAlive) { @@ -11693,7 +11950,7 @@ if (true) }; HubConnection.prototype.invokeClientMethod = function (invocationMessage) { var _this = this; - var methods = this.methods.get(invocationMessage.target.toLowerCase()); + var methods = this.methods[invocationMessage.target.toLowerCase()]; if (methods) { methods.forEach(function (m) { return m.apply(_this, invocationMessage.arguments); }); if (invocationMessage.invocationId) { @@ -11709,34 +11966,35 @@ if (true) }; HubConnection.prototype.connectionClosed = function (error) { var _this = this; - this.callbacks.forEach(function (callback) { - callback(undefined, error ? error : new Error("Invocation canceled due to connection being closed.")); - }); - this.callbacks.clear(); - this.closedCallbacks.forEach(function (c) { return c.apply(_this, [error]); }); + var callbacks = this.callbacks; + this.callbacks = {}; + Object.keys(callbacks) + .forEach(function (key) { + var callback = callbacks[key]; + callback(undefined, error ? error : new Error("Invocation canceled due to connection being closed.")); + }); this.cleanupTimeout(); + this.closedCallbacks.forEach(function (c) { return c.apply(_this, [error]); }); }; HubConnection.prototype.start = function () { return __awaiter(this, void 0, void 0, function () { - var requestedTransferMode, actualTransferMode; return __generator(this, function (_a) { switch (_a.label) { case 0: - requestedTransferMode = (this.protocol.type === 2 /* Binary */) - ? 2 /* Binary */ - : 1 /* Text */; - this.connection.features.transferMode = requestedTransferMode; - return [4 /*yield*/, this.connection.start()]; + this.logger.log(ILogger.LogLevel.Trace, "Starting HubConnection."); + this.receivedHandshakeResponse = false; + return [4 /*yield*/, this.connection.start(this.protocol.transferFormat)]; case 1: _a.sent(); - actualTransferMode = this.connection.features.transferMode; - return [4 /*yield*/, this.connection.send(TextMessageFormat_1.TextMessageFormat.write(JSON.stringify({ protocol: this.protocol.name })))]; + this.logger.log(ILogger.LogLevel.Trace, "Sending handshake request."); + // Handshake request is always JSON + return [4 /*yield*/, this.connection.send(TextMessageFormat_1.TextMessageFormat.write(JSON.stringify({ protocol: this.protocol.name, version: this.protocol.version })))]; case 2: + // Handshake request is always JSON _a.sent(); this.logger.log(ILogger.LogLevel.Information, "Using HubProtocol '" + this.protocol.name + "'."); - if (requestedTransferMode === 2 /* Binary */ && actualTransferMode === 1 /* Text */) { - this.protocol = new Base64EncodedHubProtocol_1.Base64EncodedHubProtocol(this.protocol); - } + // defensively cleanup timeout in case we receive a message from the server before we finish start + this.cleanupTimeout(); this.configureTimeout(); return [2 /*return*/]; } @@ -11744,6 +12002,7 @@ if (true) }); }; HubConnection.prototype.stop = function () { + this.logger.log(ILogger.LogLevel.Trace, "Stopping HubConnection."); this.cleanupTimeout(); return this.connection.stop(); }; @@ -11756,33 +12015,32 @@ if (true) var invocationDescriptor = this.createStreamInvocation(methodName, args); var subject = new Observable.Subject(function () { var cancelInvocation = _this.createCancelInvocation(invocationDescriptor.invocationId); - var message = _this.protocol.writeMessage(cancelInvocation); - _this.callbacks.delete(invocationDescriptor.invocationId); - return _this.connection.send(message); + var cancelMessage = _this.protocol.writeMessage(cancelInvocation); + delete _this.callbacks[invocationDescriptor.invocationId]; + return _this.connection.send(cancelMessage); }); - this.callbacks.set(invocationDescriptor.invocationId, function (invocationEvent, error) { + this.callbacks[invocationDescriptor.invocationId] = function (invocationEvent, error) { if (error) { subject.error(error); return; } if (invocationEvent.type === 3 /* Completion */) { - var completionMessage = invocationEvent; - if (completionMessage.error) { - subject.error(new Error(completionMessage.error)); + if (invocationEvent.error) { + subject.error(new Error(invocationEvent.error)); } else { subject.complete(); } } else { - subject.next(invocationEvent.item); + subject.next((invocationEvent.item)); } - }); + }; var message = this.protocol.writeMessage(invocationDescriptor); this.connection.send(message) .catch(function (e) { subject.error(e); - _this.callbacks.delete(invocationDescriptor.invocationId); + delete _this.callbacks[invocationDescriptor.invocationId]; }); return subject; }; @@ -11803,7 +12061,7 @@ if (true) } var invocationDescriptor = this.createInvocation(methodName, args, false); var p = new Promise(function (resolve, reject) { - _this.callbacks.set(invocationDescriptor.invocationId, function (invocationEvent, error) { + _this.callbacks[invocationDescriptor.invocationId] = function (invocationEvent, error) { if (error) { reject(error); return; @@ -11820,38 +12078,50 @@ if (true) else { reject(new Error("Unexpected message type: " + invocationEvent.type)); } - }); + }; var message = _this.protocol.writeMessage(invocationDescriptor); _this.connection.send(message) .catch(function (e) { reject(e); - _this.callbacks.delete(invocationDescriptor.invocationId); + delete _this.callbacks[invocationDescriptor.invocationId]; }); }); return p; }; - HubConnection.prototype.on = function (methodName, method) { - if (!methodName || !method) { + HubConnection.prototype.on = function (methodName, newMethod) { + if (!methodName || !newMethod) { return; } methodName = methodName.toLowerCase(); - if (!this.methods.has(methodName)) { - this.methods.set(methodName, []); + if (!this.methods[methodName]) { + this.methods[methodName] = []; } - this.methods.get(methodName).push(method); + // Preventing adding the same handler multiple times. + if (this.methods[methodName].indexOf(newMethod) !== -1) { + return; + } + this.methods[methodName].push(newMethod); }; HubConnection.prototype.off = function (methodName, method) { - if (!methodName || !method) { + if (!methodName) { return; } methodName = methodName.toLowerCase(); - var handlers = this.methods.get(methodName); + var handlers = this.methods[methodName]; if (!handlers) { return; } - var removeIdx = handlers.indexOf(method); - if (removeIdx != -1) { - handlers.splice(removeIdx, 1); + if (method) { + var removeIdx = handlers.indexOf(method); + if (removeIdx !== -1) { + handlers.splice(removeIdx, 1); + if (handlers.length === 0) { + delete this.methods[methodName]; + } + } + } + else { + delete this.methods[methodName]; } }; HubConnection.prototype.onclose = function (callback) { @@ -11867,19 +12137,19 @@ if (true) HubConnection.prototype.createInvocation = function (methodName, args, nonblocking) { if (nonblocking) { return { - type: 1 /* Invocation */, - target: methodName, arguments: args, + target: methodName, + type: 1 /* Invocation */, }; } else { var id = this.id; this.id++; return { - type: 1 /* Invocation */, + arguments: args, invocationId: id.toString(), target: methodName, - arguments: args, + type: 1 /* Invocation */, }; } }; @@ -11887,16 +12157,16 @@ if (true) var id = this.id; this.id++; return { - type: 4 /* StreamInvocation */, + arguments: args, invocationId: id.toString(), target: methodName, - arguments: args, + type: 4 /* StreamInvocation */, }; }; HubConnection.prototype.createCancelInvocation = function (id) { return { - type: 5 /* CancelInvocation */, invocationId: id, + type: 5 /* CancelInvocation */, }; }; return HubConnection; diff --git a/ASP.NET Core Demo/WebApplication.React/wwwroot/lib/dotnetify-react.js b/ASP.NET Core Demo/WebApplication.React/wwwroot/lib/dotnetify-react.js index 3ba08db2..406f4147 100644 --- a/ASP.NET Core Demo/WebApplication.React/wwwroot/lib/dotnetify-react.js +++ b/ASP.NET Core Demo/WebApplication.React/wwwroot/lib/dotnetify-react.js @@ -69,7 +69,7 @@ var dotnetify = typeof dotnetify === "undefined" ? {} : dotnetify; }); dotnetify.react = $.extend(dotnetify.hasOwnProperty("react") ? dotnetify.react : {}, { - version: "1.0.5-beta", + version: "1.1.0", viewModels: {}, plugins: {}, @@ -83,6 +83,12 @@ var dotnetify = typeof dotnetify === "undefined" ? {} : dotnetify; // Setup SignalR server method handler. dotnetifyHub.client.response_VM = function (iVMId, iVMData) { + // SignalR .NET Core is sending an array of arguments. + if (Array.isArray(iVMId)) { + iVMData = iVMId[1]; + iVMId = iVMId[0]; + } + var vm = self.viewModels.hasOwnProperty(iVMId) ? self.viewModels[iVMId] : null; // Handle server-side exception. @@ -430,7 +436,7 @@ var dotnetify = typeof dotnetify === "undefined" ? {} : dotnetify; } var match = this.State()[iListName].filter(function (i) { return i[key] == iNewItem[key] }); if (match.length > 0) { - console.error("[" + this.$vmId + "] couldn't add item to '" + listName + "' because the key already exists"); + console.error("[" + this.$vmId + "] couldn't add item to '" + iListName + "' because the key already exists"); return; } } diff --git a/ASP.NET Core Demo/WebApplication.React/wwwroot/lib/dotnetify-react.router.js b/ASP.NET Core Demo/WebApplication.React/wwwroot/lib/dotnetify-react.router.js index c4efed95..b6c13578 100644 --- a/ASP.NET Core Demo/WebApplication.React/wwwroot/lib/dotnetify-react.router.js +++ b/ASP.NET Core Demo/WebApplication.React/wwwroot/lib/dotnetify-react.router.js @@ -227,7 +227,7 @@ limitations under the License. // Add plugin functions. dotnetify.react.router = { - version: "1.0.6-beta", + version: "1.1.0", // URL path that will be parsed when performing routing. urlPath: document.location.pathname, diff --git a/ASP.NET Core Demo/WebApplication.React/wwwroot/lib/dotnetify-react.scope.js b/ASP.NET Core Demo/WebApplication.React/wwwroot/lib/dotnetify-react.scope.js index 965338a3..c322bee7 100644 --- a/ASP.NET Core Demo/WebApplication.React/wwwroot/lib/dotnetify-react.scope.js +++ b/ASP.NET Core Demo/WebApplication.React/wwwroot/lib/dotnetify-react.scope.js @@ -35,7 +35,7 @@ limitations under the License. // injects properties and dispatch functions into the component. dotnetify.react.Scope = createReactClass({ displayName: "Scope", - version: "1.0.2", + version: "1.1.0", propTypes: { vm: PropTypes.string }, contextTypes: { scoped: PropTypes.func }, diff --git a/ASP.NET Core Demo/WebApplication.React/wwwroot/lib/signalR-netcore.js b/ASP.NET Core Demo/WebApplication.React/wwwroot/lib/signalR-netcore.js index 6b399e65..ee951f3e 100644 --- a/ASP.NET Core Demo/WebApplication.React/wwwroot/lib/signalR-netcore.js +++ b/ASP.NET Core Demo/WebApplication.React/wwwroot/lib/signalR-netcore.js @@ -1,7 +1,7 @@ /* @license * Copyright (c) .NET Foundation. All rights reserved. * Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -*/ + */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : @@ -1230,8 +1230,14 @@ var HttpError = /** @class */ (function (_super) { __extends(HttpError, _super); function HttpError(errorMessage, statusCode) { - var _this = _super.call(this, errorMessage) || this; + var _newTarget = this.constructor; + var _this = this; + var trueProto = _newTarget.prototype; + _this = _super.call(this, errorMessage) || this; _this.statusCode = statusCode; + // Workaround issue in Typescript compiler + // https://github.com/Microsoft/TypeScript/issues/13965#issuecomment-278570200 + _this.__proto__ = trueProto; return _this; } return HttpError; @@ -1240,8 +1246,15 @@ var TimeoutError = /** @class */ (function (_super) { __extends(TimeoutError, _super); function TimeoutError(errorMessage) { + var _newTarget = this.constructor; if (errorMessage === void 0) { errorMessage = "A timeout occurred."; } - return _super.call(this, errorMessage) || this; + var _this = this; + var trueProto = _newTarget.prototype; + _this = _super.call(this, errorMessage) || this; + // Workaround issue in Typescript compiler + // https://github.com/Microsoft/TypeScript/issues/13965#issuecomment-278570200 + _this.__proto__ = trueProto; + return _this; } return TimeoutError; }(Error)); @@ -1253,6 +1266,22 @@ var Errors_1 = Errors.HttpError; var Errors_2 = Errors.TimeoutError; + var ILogger = createCommonjsModule(function (module, exports) { + Object.defineProperty(exports, "__esModule", { value: true }); + var LogLevel; + (function (LogLevel) { + LogLevel[LogLevel["Trace"] = 0] = "Trace"; + LogLevel[LogLevel["Information"] = 1] = "Information"; + LogLevel[LogLevel["Warning"] = 2] = "Warning"; + LogLevel[LogLevel["Error"] = 3] = "Error"; + LogLevel[LogLevel["None"] = 4] = "None"; + })(LogLevel = exports.LogLevel || (exports.LogLevel = {})); + + }); + + unwrapExports(ILogger); + var ILogger_1 = ILogger.LogLevel; + var HttpClient_1 = createCommonjsModule(function (module, exports) { var __extends = (commonjsGlobal && commonjsGlobal.__extends) || (function () { var extendStatics = Object.setPrototypeOf || @@ -1274,6 +1303,7 @@ }; Object.defineProperty(exports, "__esModule", { value: true }); + var HttpResponse = /** @class */ (function () { function HttpResponse(statusCode, statusText, content) { this.statusCode = statusCode; @@ -1297,16 +1327,21 @@ exports.HttpClient = HttpClient; var DefaultHttpClient = /** @class */ (function (_super) { __extends(DefaultHttpClient, _super); - function DefaultHttpClient() { - return _super !== null && _super.apply(this, arguments) || this; + function DefaultHttpClient(logger) { + var _this = _super.call(this) || this; + _this.logger = logger; + return _this; } DefaultHttpClient.prototype.send = function (request) { + var _this = this; return new Promise(function (resolve, reject) { var xhr = new XMLHttpRequest(); xhr.open(request.method, request.url, true); + xhr.withCredentials = true; xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest"); if (request.headers) { - request.headers.forEach(function (value, header) { return xhr.setRequestHeader(header, value); }); + Object.keys(request.headers) + .forEach(function (header) { return xhr.setRequestHeader(header, request.headers[header]); }); } if (request.responseType) { xhr.responseType = request.responseType; @@ -1331,9 +1366,11 @@ } }; xhr.onerror = function () { + _this.logger.log(ILogger.LogLevel.Warning, "Error from HTTP request. " + xhr.status + ": " + xhr.statusText); reject(new Errors.HttpError(xhr.statusText, xhr.status)); }; xhr.ontimeout = function () { + _this.logger.log(ILogger.LogLevel.Warning, "Timeout from HTTP request."); reject(new Errors.TimeoutError()); }; xhr.send(request.content || ""); @@ -1350,21 +1387,67 @@ var HttpClient_3 = HttpClient_1.HttpClient; var HttpClient_4 = HttpClient_1.DefaultHttpClient; - var ILogger = createCommonjsModule(function (module, exports) { + var Loggers = createCommonjsModule(function (module, exports) { Object.defineProperty(exports, "__esModule", { value: true }); - var LogLevel; - (function (LogLevel) { - LogLevel[LogLevel["Trace"] = 0] = "Trace"; - LogLevel[LogLevel["Information"] = 1] = "Information"; - LogLevel[LogLevel["Warning"] = 2] = "Warning"; - LogLevel[LogLevel["Error"] = 3] = "Error"; - LogLevel[LogLevel["None"] = 4] = "None"; - })(LogLevel = exports.LogLevel || (exports.LogLevel = {})); + + var NullLogger = /** @class */ (function () { + function NullLogger() { + } + NullLogger.prototype.log = function (logLevel, message) { + }; + return NullLogger; + }()); + exports.NullLogger = NullLogger; + var ConsoleLogger = /** @class */ (function () { + function ConsoleLogger(minimumLogLevel) { + this.minimumLogLevel = minimumLogLevel; + } + ConsoleLogger.prototype.log = function (logLevel, message) { + if (logLevel >= this.minimumLogLevel) { + switch (logLevel) { + case ILogger.LogLevel.Error: + console.error(ILogger.LogLevel[logLevel] + ": " + message); + break; + case ILogger.LogLevel.Warning: + console.warn(ILogger.LogLevel[logLevel] + ": " + message); + break; + case ILogger.LogLevel.Information: + console.info(ILogger.LogLevel[logLevel] + ": " + message); + break; + default: + console.log(ILogger.LogLevel[logLevel] + ": " + message); + break; + } + } + }; + return ConsoleLogger; + }()); + exports.ConsoleLogger = ConsoleLogger; + var LoggerFactory = /** @class */ (function () { + function LoggerFactory() { + } + LoggerFactory.createLogger = function (logging) { + if (logging === undefined) { + return new ConsoleLogger(ILogger.LogLevel.Information); + } + if (logging === null) { + return new NullLogger(); + } + if (logging.log) { + return logging; + } + return new ConsoleLogger(logging); + }; + return LoggerFactory; + }()); + exports.LoggerFactory = LoggerFactory; }); - unwrapExports(ILogger); - var ILogger_1 = ILogger.LogLevel; + unwrapExports(Loggers); + var Loggers_1 = Loggers.NullLogger; + var Loggers_2 = Loggers.ConsoleLogger; + var Loggers_3 = Loggers.LoggerFactory; var AbortController_1 = createCommonjsModule(function (module, exports) { Object.defineProperty(exports, "__esModule", { value: true }); @@ -1406,6 +1489,31 @@ unwrapExports(AbortController_1); var AbortController_2 = AbortController_1.AbortController; + var Utils = createCommonjsModule(function (module, exports) { + Object.defineProperty(exports, "__esModule", { value: true }); + var Arg = /** @class */ (function () { + function Arg() { + } + Arg.isRequired = function (val, name) { + if (val === null || val === undefined) { + throw new Error("The '" + name + "' argument is required."); + } + }; + Arg.isIn = function (val, values, name) { + // TypeScript enums have keys for **both** the name and the value of each enum member on the type itself. + if (!(val in values)) { + throw new Error("Unknown " + name + " value: " + val + "."); + } + }; + return Arg; + }()); + exports.Arg = Arg; + + }); + + unwrapExports(Utils); + var Utils_1 = Utils.Arg; + var Transports = createCommonjsModule(function (module, exports) { var __awaiter = (commonjsGlobal && commonjsGlobal.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { @@ -1446,19 +1554,33 @@ + var TransportType; (function (TransportType) { TransportType[TransportType["WebSockets"] = 0] = "WebSockets"; TransportType[TransportType["ServerSentEvents"] = 1] = "ServerSentEvents"; TransportType[TransportType["LongPolling"] = 2] = "LongPolling"; })(TransportType = exports.TransportType || (exports.TransportType = {})); + var TransferFormat; + (function (TransferFormat) { + TransferFormat[TransferFormat["Text"] = 1] = "Text"; + TransferFormat[TransferFormat["Binary"] = 2] = "Binary"; + })(TransferFormat = exports.TransferFormat || (exports.TransferFormat = {})); var WebSocketTransport = /** @class */ (function () { function WebSocketTransport(accessTokenFactory, logger) { this.logger = logger; this.accessTokenFactory = accessTokenFactory || (function () { return null; }); } - WebSocketTransport.prototype.connect = function (url, requestedTransferMode, connection) { + WebSocketTransport.prototype.connect = function (url, transferFormat, connection) { var _this = this; + Utils.Arg.isRequired(url, "url"); + Utils.Arg.isRequired(transferFormat, "transferFormat"); + Utils.Arg.isIn(transferFormat, TransferFormat, "transferFormat"); + Utils.Arg.isRequired(connection, "connection"); + if (typeof (WebSocket) === "undefined") { + throw new Error("'WebSocket' is not supported in your environment."); + } + this.logger.log(ILogger.LogLevel.Trace, "(WebSockets transport) Connecting"); return new Promise(function (resolve, reject) { url = url.replace(/^http/, "ws"); var token = _this.accessTokenFactory(); @@ -1466,19 +1588,19 @@ url += (url.indexOf("?") < 0 ? "?" : "&") + ("access_token=" + encodeURIComponent(token)); } var webSocket = new WebSocket(url); - if (requestedTransferMode == 2 /* Binary */) { + if (transferFormat === TransferFormat.Binary) { webSocket.binaryType = "arraybuffer"; } webSocket.onopen = function (event) { _this.logger.log(ILogger.LogLevel.Information, "WebSocket connected to " + url); _this.webSocket = webSocket; - resolve(requestedTransferMode); + resolve(); }; webSocket.onerror = function (event) { - reject(); + reject(event.error); }; webSocket.onmessage = function (message) { - _this.logger.log(ILogger.LogLevel.Trace, "(WebSockets transport) data received: " + message.data); + _this.logger.log(ILogger.LogLevel.Trace, "(WebSockets transport) data received. " + getDataDetail(message.data) + "."); if (_this.onreceive) { _this.onreceive(message.data); } @@ -1498,6 +1620,7 @@ }; WebSocketTransport.prototype.send = function (data) { if (this.webSocket && this.webSocket.readyState === WebSocket.OPEN) { + this.logger.log(ILogger.LogLevel.Trace, "(WebSockets transport) sending data. " + getDataDetail(data) + "."); this.webSocket.send(data); return Promise.resolve(); } @@ -1519,23 +1642,31 @@ this.accessTokenFactory = accessTokenFactory || (function () { return null; }); this.logger = logger; } - ServerSentEventsTransport.prototype.connect = function (url, requestedTransferMode, connection) { + ServerSentEventsTransport.prototype.connect = function (url, transferFormat, connection) { var _this = this; + Utils.Arg.isRequired(url, "url"); + Utils.Arg.isRequired(transferFormat, "transferFormat"); + Utils.Arg.isIn(transferFormat, TransferFormat, "transferFormat"); + Utils.Arg.isRequired(connection, "connection"); if (typeof (EventSource) === "undefined") { - Promise.reject("EventSource not supported by the browser."); + throw new Error("'EventSource' is not supported in your environment."); } + this.logger.log(ILogger.LogLevel.Trace, "(SSE transport) Connecting"); this.url = url; return new Promise(function (resolve, reject) { + if (transferFormat !== TransferFormat.Text) { + reject(new Error("The Server-Sent Events transport only supports the 'Text' transfer format")); + } var token = _this.accessTokenFactory(); if (token) { url += (url.indexOf("?") < 0 ? "?" : "&") + ("access_token=" + encodeURIComponent(token)); } - var eventSource = new EventSource(url); + var eventSource = new EventSource(url, { withCredentials: true }); try { eventSource.onmessage = function (e) { if (_this.onreceive) { try { - _this.logger.log(ILogger.LogLevel.Trace, "(SSE transport) data received: " + e.data); + _this.logger.log(ILogger.LogLevel.Trace, "(SSE transport) data received. " + getDataDetail(e.data) + "."); _this.onreceive(e.data); } catch (error) { @@ -1547,7 +1678,7 @@ } }; eventSource.onerror = function (e) { - reject(); + reject(new Error(e.message || "Error occurred")); // don't report an error if the transport did not start successfully if (_this.eventSource && _this.onclose) { _this.onclose(new Error(e.message || "Error occurred")); @@ -1557,7 +1688,7 @@ _this.logger.log(ILogger.LogLevel.Information, "SSE connected to " + _this.url); _this.eventSource = eventSource; // SSE is a text protocol - resolve(1 /* Text */); + resolve(); }; } catch (e) { @@ -1568,7 +1699,7 @@ ServerSentEventsTransport.prototype.send = function (data) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { - return [2 /*return*/, send(this.httpClient, this.url, this.accessTokenFactory, data)]; + return [2 /*return*/, send(this.logger, "SSE", this.httpClient, this.url, this.accessTokenFactory, data)]; }); }); }; @@ -1589,34 +1720,40 @@ this.logger = logger; this.pollAbort = new AbortController_1.AbortController(); } - LongPollingTransport.prototype.connect = function (url, requestedTransferMode, connection) { + LongPollingTransport.prototype.connect = function (url, transferFormat, connection) { + Utils.Arg.isRequired(url, "url"); + Utils.Arg.isRequired(transferFormat, "transferFormat"); + Utils.Arg.isIn(transferFormat, TransferFormat, "transferFormat"); + Utils.Arg.isRequired(connection, "connection"); this.url = url; + this.logger.log(ILogger.LogLevel.Trace, "(LongPolling transport) Connecting"); // Set a flag indicating we have inherent keep-alive in this transport. connection.features.inherentKeepAlive = true; - if (requestedTransferMode === 2 /* Binary */ && (typeof new XMLHttpRequest().responseType !== "string")) { + if (transferFormat === TransferFormat.Binary && (typeof new XMLHttpRequest().responseType !== "string")) { // This will work if we fix: https://github.com/aspnet/SignalR/issues/742 throw new Error("Binary protocols over XmlHttpRequest not implementing advanced features are not supported."); } - this.poll(this.url, requestedTransferMode); - return Promise.resolve(requestedTransferMode); + this.poll(this.url, transferFormat); + return Promise.resolve(); }; - LongPollingTransport.prototype.poll = function (url, transferMode) { + LongPollingTransport.prototype.poll = function (url, transferFormat) { return __awaiter(this, void 0, void 0, function () { var pollOptions, token, pollUrl, response, e_1; return __generator(this, function (_a) { switch (_a.label) { case 0: pollOptions = { - timeout: 120000, abortSignal: this.pollAbort.signal, - headers: new Map(), + headers: {}, + timeout: 90000, }; - if (transferMode === 2 /* Binary */) { + if (transferFormat === TransferFormat.Binary) { pollOptions.responseType = "arraybuffer"; } token = this.accessTokenFactory(); if (token) { - pollOptions.headers.set("Authorization", "Bearer " + token); + // tslint:disable-next-line:no-string-literal + pollOptions.headers["Authorization"] = "Bearer " + token; } _a.label = 1; case 1: @@ -1648,7 +1785,7 @@ else { // Process the response if (response.content) { - this.logger.log(ILogger.LogLevel.Trace, "(LongPolling transport) data received: " + response.content); + this.logger.log(ILogger.LogLevel.Trace, "(LongPolling transport) data received. " + getDataDetail(response.content) + "."); if (this.onreceive) { this.onreceive(response.content); } @@ -1682,7 +1819,7 @@ LongPollingTransport.prototype.send = function (data) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { - return [2 /*return*/, send(this.httpClient, this.url, this.accessTokenFactory, data)]; + return [2 /*return*/, send(this.logger, "LongPolling", this.httpClient, this.url, this.accessTokenFactory, data)]; }); }); }; @@ -1693,23 +1830,34 @@ return LongPollingTransport; }()); exports.LongPollingTransport = LongPollingTransport; - function send(httpClient, url, accessTokenFactory, content) { + function getDataDetail(data) { + var length = null; + if (data instanceof ArrayBuffer) { + length = "Binary data of length " + data.byteLength; + } + else if (typeof data === "string") { + length = "String data of length " + data.length; + } + return length; + } + function send(logger, transportName, httpClient, url, accessTokenFactory, content) { return __awaiter(this, void 0, void 0, function () { - var headers, token; - return __generator(this, function (_a) { - switch (_a.label) { + var headers, token, response, _a; + return __generator(this, function (_b) { + switch (_b.label) { case 0: token = accessTokenFactory(); if (token) { - headers = new Map(); - headers.set("Authorization", "Bearer " + accessTokenFactory()); + headers = (_a = {}, _a["Authorization"] = "Bearer " + accessTokenFactory(), _a); } + logger.log(ILogger.LogLevel.Trace, "(" + transportName + " transport) sending data. " + getDataDetail(content) + "."); return [4 /*yield*/, httpClient.post(url, { content: content, - headers: headers + headers: headers, })]; case 1: - _a.sent(); + response = _b.sent(); + logger.log(ILogger.LogLevel.Trace, "(" + transportName + " transport) request complete. Response status: " + response.statusCode + "."); return [2 /*return*/]; } }); @@ -1720,69 +1868,10 @@ unwrapExports(Transports); var Transports_1 = Transports.TransportType; - var Transports_2 = Transports.WebSocketTransport; - var Transports_3 = Transports.ServerSentEventsTransport; - var Transports_4 = Transports.LongPollingTransport; - - var Loggers = createCommonjsModule(function (module, exports) { - Object.defineProperty(exports, "__esModule", { value: true }); - - var NullLogger = /** @class */ (function () { - function NullLogger() { - } - NullLogger.prototype.log = function (logLevel, message) { - }; - return NullLogger; - }()); - exports.NullLogger = NullLogger; - var ConsoleLogger = /** @class */ (function () { - function ConsoleLogger(minimumLogLevel) { - this.minimumLogLevel = minimumLogLevel; - } - ConsoleLogger.prototype.log = function (logLevel, message) { - if (logLevel >= this.minimumLogLevel) { - switch (logLevel) { - case ILogger.LogLevel.Error: - console.error(ILogger.LogLevel[logLevel] + ": " + message); - break; - case ILogger.LogLevel.Warning: - console.warn(ILogger.LogLevel[logLevel] + ": " + message); - break; - case ILogger.LogLevel.Information: - console.info(ILogger.LogLevel[logLevel] + ": " + message); - break; - default: - console.log(ILogger.LogLevel[logLevel] + ": " + message); - break; - } - } - }; - return ConsoleLogger; - }()); - exports.ConsoleLogger = ConsoleLogger; - var LoggerFactory; - (function (LoggerFactory) { - function createLogger(logging) { - if (logging === undefined) { - return new ConsoleLogger(ILogger.LogLevel.Information); - } - if (logging === null) { - return new NullLogger(); - } - if (logging.log) { - return logging; - } - return new ConsoleLogger(logging); - } - LoggerFactory.createLogger = createLogger; - })(LoggerFactory = exports.LoggerFactory || (exports.LoggerFactory = {})); - - }); - - unwrapExports(Loggers); - var Loggers_1 = Loggers.NullLogger; - var Loggers_2 = Loggers.ConsoleLogger; - var Loggers_3 = Loggers.LoggerFactory; + var Transports_2 = Transports.TransferFormat; + var Transports_3 = Transports.WebSocketTransport; + var Transports_4 = Transports.ServerSentEventsTransport; + var Transports_5 = Transports.LongPollingTransport; var HttpConnection_1 = createCommonjsModule(function (module, exports) { var __awaiter = (commonjsGlobal && commonjsGlobal.__awaiter) || function (thisArg, _arguments, P, generator) { @@ -1825,131 +1914,231 @@ + var HttpConnection = /** @class */ (function () { function HttpConnection(url, options) { if (options === void 0) { options = {}; } this.features = {}; + Utils.Arg.isRequired(url, "url"); this.logger = Loggers.LoggerFactory.createLogger(options.logger); this.baseUrl = this.resolveUrl(url); options = options || {}; options.accessTokenFactory = options.accessTokenFactory || (function () { return null; }); - this.httpClient = options.httpClient || new HttpClient_1.DefaultHttpClient(); + this.httpClient = options.httpClient || new HttpClient_1.DefaultHttpClient(this.logger); this.connectionState = 2 /* Disconnected */; this.options = options; } - HttpConnection.prototype.start = function () { - return __awaiter(this, void 0, void 0, function () { - return __generator(this, function (_a) { - if (this.connectionState !== 2 /* Disconnected */) { - return [2 /*return*/, Promise.reject(new Error("Cannot start a connection that is not in the 'Disconnected' state."))]; - } - this.connectionState = 0 /* Connecting */; - this.startPromise = this.startInternal(); - return [2 /*return*/, this.startPromise]; - }); - }); + HttpConnection.prototype.start = function (transferFormat) { + Utils.Arg.isRequired(transferFormat, "transferFormat"); + Utils.Arg.isIn(transferFormat, Transports.TransferFormat, "transferFormat"); + this.logger.log(ILogger.LogLevel.Trace, "Starting connection with transfer format '" + Transports.TransferFormat[transferFormat] + "'."); + if (this.connectionState !== 2 /* Disconnected */) { + return Promise.reject(new Error("Cannot start a connection that is not in the 'Disconnected' state.")); + } + this.connectionState = 0 /* Connecting */; + this.startPromise = this.startInternal(transferFormat); + return this.startPromise; }; - HttpConnection.prototype.startInternal = function () { + HttpConnection.prototype.startInternal = function (transferFormat) { return __awaiter(this, void 0, void 0, function () { var _this = this; - var headers, token, negotiatePayload, negotiateResponse, requestedTransferMode, _a, e_1; + var token, headers, negotiateResponse, e_1, _a; return __generator(this, function (_b) { switch (_b.label) { case 0: - _b.trys.push([0, 5, , 6]); - if (!(this.options.transport === Transports.TransportType.WebSockets)) return [3 /*break*/, 1]; + _b.trys.push([0, 6, , 7]); + if (!(this.options.transport === Transports.TransportType.WebSockets)) return [3 /*break*/, 2]; // No need to add a connection ID in this case this.url = this.baseUrl; - this.transport = this.createTransport(this.options.transport, [Transports.TransportType[Transports.TransportType.WebSockets]]); - return [3 /*break*/, 3]; + this.transport = this.constructTransport(Transports.TransportType.WebSockets); + // We should just call connect directly in this case. + // No fallback or negotiate in this case. + return [4 /*yield*/, this.transport.connect(this.url, transferFormat, this)]; case 1: - headers = void 0; + // We should just call connect directly in this case. + // No fallback or negotiate in this case. + _b.sent(); + return [3 /*break*/, 5]; + case 2: token = this.options.accessTokenFactory(); + headers = void 0; if (token) { - headers = new Map(); - headers.set("Authorization", "Bearer " + token); + headers = (_a = {}, _a["Authorization"] = "Bearer " + token, _a); } - return [4 /*yield*/, this.httpClient.post(this.resolveNegotiateUrl(this.baseUrl), { - content: "", - headers: headers - })]; - case 2: - negotiatePayload = _b.sent(); - negotiateResponse = JSON.parse(negotiatePayload.content); - this.connectionId = negotiateResponse.connectionId; + return [4 /*yield*/, this.getNegotiationResponse(headers)]; + case 3: + negotiateResponse = _b.sent(); // the user tries to stop the the connection when it is being started - if (this.connectionState == 2 /* Disconnected */) { + if (this.connectionState === 2 /* Disconnected */) { return [2 /*return*/]; } - if (this.connectionId) { - this.url = this.baseUrl + (this.baseUrl.indexOf("?") === -1 ? "?" : "&") + ("id=" + this.connectionId); - this.transport = this.createTransport(this.options.transport, negotiateResponse.availableTransports); - } - _b.label = 3; - case 3: + return [4 /*yield*/, this.createTransport(this.options.transport, negotiateResponse, transferFormat, headers)]; + case 4: + _b.sent(); + _b.label = 5; + case 5: this.transport.onreceive = this.onreceive; this.transport.onclose = function (e) { return _this.stopConnection(true, e); }; - requestedTransferMode = this.features.transferMode === 2 /* Binary */ - ? 2 /* Binary */ - : 1 /* Text */; - _a = this.features; - return [4 /*yield*/, this.transport.connect(this.url, requestedTransferMode, this)]; - case 4: - _a.transferMode = _b.sent(); // only change the state if we were connecting to not overwrite // the state if the connection is already marked as Disconnected this.changeState(0 /* Connecting */, 1 /* Connected */); - return [3 /*break*/, 6]; - case 5: + return [3 /*break*/, 7]; + case 6: e_1 = _b.sent(); - this.logger.log(ILogger.LogLevel.Error, "Failed to start the connection. " + e_1); + this.logger.log(ILogger.LogLevel.Error, "Failed to start the connection: " + e_1); this.connectionState = 2 /* Disconnected */; this.transport = null; throw e_1; + case 7: return [2 /*return*/]; + } + }); + }); + }; + HttpConnection.prototype.getNegotiationResponse = function (headers) { + return __awaiter(this, void 0, void 0, function () { + var negotiateUrl, response, e_2; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + negotiateUrl = this.resolveNegotiateUrl(this.baseUrl); + this.logger.log(ILogger.LogLevel.Trace, "Sending negotiation request: " + negotiateUrl); + _a.label = 1; + case 1: + _a.trys.push([1, 3, , 4]); + return [4 /*yield*/, this.httpClient.post(negotiateUrl, { + content: "", + headers: headers, + })]; + case 2: + response = _a.sent(); + return [2 /*return*/, JSON.parse(response.content)]; + case 3: + e_2 = _a.sent(); + this.logger.log(ILogger.LogLevel.Error, "Failed to complete negotiation with the server: " + e_2); + throw e_2; + case 4: return [2 /*return*/]; + } + }); + }); + }; + HttpConnection.prototype.updateConnectionId = function (negotiateResponse) { + this.connectionId = negotiateResponse.connectionId; + this.url = this.baseUrl + (this.baseUrl.indexOf("?") === -1 ? "?" : "&") + ("id=" + this.connectionId); + }; + HttpConnection.prototype.createTransport = function (requestedTransport, negotiateResponse, requestedTransferFormat, headers) { + return __awaiter(this, void 0, void 0, function () { + var transports, _i, transports_1, endpoint, transport, ex_1; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + this.updateConnectionId(negotiateResponse); + if (!this.isITransport(requestedTransport)) return [3 /*break*/, 2]; + this.logger.log(ILogger.LogLevel.Trace, "Connection was provided an instance of ITransport, using that directly."); + this.transport = requestedTransport; + return [4 /*yield*/, this.transport.connect(this.url, requestedTransferFormat, this)]; + case 1: + _a.sent(); + // only change the state if we were connecting to not overwrite + // the state if the connection is already marked as Disconnected + this.changeState(0 /* Connecting */, 1 /* Connected */); + return [2 /*return*/]; + case 2: + transports = negotiateResponse.availableTransports; + _i = 0, transports_1 = transports; + _a.label = 3; + case 3: + if (!(_i < transports_1.length)) return [3 /*break*/, 9]; + endpoint = transports_1[_i]; + this.connectionState = 0 /* Connecting */; + transport = this.resolveTransport(endpoint, requestedTransport, requestedTransferFormat); + if (!(typeof transport === "number")) return [3 /*break*/, 8]; + this.transport = this.constructTransport(transport); + if (!(negotiateResponse.connectionId === null)) return [3 /*break*/, 5]; + return [4 /*yield*/, this.getNegotiationResponse(headers)]; + case 4: + negotiateResponse = _a.sent(); + this.updateConnectionId(negotiateResponse); + _a.label = 5; + case 5: + _a.trys.push([5, 7, , 8]); + return [4 /*yield*/, this.transport.connect(this.url, requestedTransferFormat, this)]; case 6: - ; + _a.sent(); + this.changeState(0 /* Connecting */, 1 /* Connected */); return [2 /*return*/]; + case 7: + ex_1 = _a.sent(); + this.logger.log(ILogger.LogLevel.Error, "Failed to start the transport '" + Transports.TransportType[transport] + "': " + ex_1); + this.connectionState = 2 /* Disconnected */; + negotiateResponse.connectionId = null; + return [3 /*break*/, 8]; + case 8: + _i++; + return [3 /*break*/, 3]; + case 9: throw new Error("Unable to initialize any of the available transports."); } }); }); }; - HttpConnection.prototype.createTransport = function (transport, availableTransports) { - if ((transport === null || transport === undefined) && availableTransports.length > 0) { - transport = Transports.TransportType[availableTransports[0]]; + HttpConnection.prototype.constructTransport = function (transport) { + switch (transport) { + case Transports.TransportType.WebSockets: + return new Transports.WebSocketTransport(this.options.accessTokenFactory, this.logger); + case Transports.TransportType.ServerSentEvents: + return new Transports.ServerSentEventsTransport(this.httpClient, this.options.accessTokenFactory, this.logger); + case Transports.TransportType.LongPolling: + return new Transports.LongPollingTransport(this.httpClient, this.options.accessTokenFactory, this.logger); + default: + throw new Error("Unknown transport: " + transport + "."); } - if (transport === Transports.TransportType.WebSockets && availableTransports.indexOf(Transports.TransportType[transport]) >= 0) { - return new Transports.WebSocketTransport(this.options.accessTokenFactory, this.logger); - } - if (transport === Transports.TransportType.ServerSentEvents && availableTransports.indexOf(Transports.TransportType[transport]) >= 0) { - return new Transports.ServerSentEventsTransport(this.httpClient, this.options.accessTokenFactory, this.logger); - } - if (transport === Transports.TransportType.LongPolling && availableTransports.indexOf(Transports.TransportType[transport]) >= 0) { - return new Transports.LongPollingTransport(this.httpClient, this.options.accessTokenFactory, this.logger); + }; + HttpConnection.prototype.resolveTransport = function (endpoint, requestedTransport, requestedTransferFormat) { + var transport = Transports.TransportType[endpoint.transport]; + if (transport === null || transport === undefined) { + this.logger.log(ILogger.LogLevel.Trace, "Skipping transport '" + endpoint.transport + "' because it is not supported by this client."); } - if (this.isITransport(transport)) { - return transport; + else { + var transferFormats = endpoint.transferFormats.map(function (s) { return Transports.TransferFormat[s]; }); + if (!requestedTransport || transport === requestedTransport) { + if (transferFormats.indexOf(requestedTransferFormat) >= 0) { + if ((transport === Transports.TransportType.WebSockets && typeof WebSocket === "undefined") || + (transport === Transports.TransportType.ServerSentEvents && typeof EventSource === "undefined")) { + this.logger.log(ILogger.LogLevel.Trace, "Skipping transport '" + Transports.TransportType[transport] + "' because it is not supported in your environment.'"); + } + else { + this.logger.log(ILogger.LogLevel.Trace, "Selecting transport '" + Transports.TransportType[transport] + "'"); + return transport; + } + } + else { + this.logger.log(ILogger.LogLevel.Trace, "Skipping transport '" + Transports.TransportType[transport] + "' because it does not support the requested transfer format '" + Transports.TransferFormat[requestedTransferFormat] + "'."); + } + } + else { + this.logger.log(ILogger.LogLevel.Trace, "Skipping transport '" + Transports.TransportType[transport] + "' because it was disabled by the client."); + } } - throw new Error("No available transports found."); + return null; }; HttpConnection.prototype.isITransport = function (transport) { return typeof (transport) === "object" && "connect" in transport; }; HttpConnection.prototype.changeState = function (from, to) { - if (this.connectionState == from) { + if (this.connectionState === from) { this.connectionState = to; return true; } return false; }; HttpConnection.prototype.send = function (data) { - if (this.connectionState != 1 /* Connected */) { - throw new Error("Cannot send data if the connection is not in the 'Connected' State"); + if (this.connectionState !== 1 /* Connected */) { + throw new Error("Cannot send data if the connection is not in the 'Connected' State."); } return this.transport.send(data); }; HttpConnection.prototype.stop = function (error) { return __awaiter(this, void 0, void 0, function () { - var previousState, e_2; + var previousState, e_3; return __generator(this, function (_a) { switch (_a.label) { case 0: @@ -1963,10 +2152,10 @@ _a.sent(); return [3 /*break*/, 4]; case 3: - e_2 = _a.sent(); + e_3 = _a.sent(); return [3 /*break*/, 4]; case 4: - this.stopConnection(/*raiseClosed*/ previousState == 1 /* Connected */, error); + this.stopConnection(/*raiseClosed*/ previousState === 1 /* Connected */, error); return [2 /*return*/]; } }); @@ -1993,7 +2182,7 @@ if (url.lastIndexOf("https://", 0) === 0 || url.lastIndexOf("http://", 0) === 0) { return url; } - if (typeof window === 'undefined' || !window || !window.document) { + if (typeof window === "undefined" || !window || !window.document) { throw new Error("Cannot resolve '" + url + "'."); } var parser = window.document.createElement("a"); @@ -2001,11 +2190,11 @@ var baseUrl = (!parser.protocol || parser.protocol === ":") ? window.document.location.protocol + "//" + (parser.host || window.document.location.host) : parser.protocol + "//" + parser.host; - if (!url || url[0] != '/') { - url = '/' + url; + if (!url || url[0] !== "/") { + url = "/" + url; } var normalizedUrl = baseUrl + url; - this.logger.log(ILogger.LogLevel.Information, "Normalizing '" + url + "' to '" + normalizedUrl + "'"); + this.logger.log(ILogger.LogLevel.Information, "Normalizing '" + url + "' to '" + normalizedUrl + "'."); return normalizedUrl; }; HttpConnection.prototype.resolveNegotiateUrl = function (url) { @@ -2027,66 +2216,6 @@ unwrapExports(HttpConnection_1); var HttpConnection_2 = HttpConnection_1.HttpConnection; - var Observable = createCommonjsModule(function (module, exports) { - Object.defineProperty(exports, "__esModule", { value: true }); - var Subscription = /** @class */ (function () { - function Subscription(subject, observer) { - this.subject = subject; - this.observer = observer; - } - Subscription.prototype.dispose = function () { - var index = this.subject.observers.indexOf(this.observer); - if (index > -1) { - this.subject.observers.splice(index, 1); - } - if (this.subject.observers.length === 0) { - this.subject.cancelCallback().catch(function (_) { }); - } - }; - return Subscription; - }()); - exports.Subscription = Subscription; - var Subject = /** @class */ (function () { - function Subject(cancelCallback) { - this.observers = []; - this.cancelCallback = cancelCallback; - } - Subject.prototype.next = function (item) { - for (var _i = 0, _a = this.observers; _i < _a.length; _i++) { - var observer = _a[_i]; - observer.next(item); - } - }; - Subject.prototype.error = function (err) { - for (var _i = 0, _a = this.observers; _i < _a.length; _i++) { - var observer = _a[_i]; - if (observer.error) { - observer.error(err); - } - } - }; - Subject.prototype.complete = function () { - for (var _i = 0, _a = this.observers; _i < _a.length; _i++) { - var observer = _a[_i]; - if (observer.complete) { - observer.complete(); - } - } - }; - Subject.prototype.subscribe = function (observer) { - this.observers.push(observer); - return new Subscription(this, observer); - }; - return Subject; - }()); - exports.Subject = Subject; - - }); - - unwrapExports(Observable); - var Observable_1 = Observable.Subscription; - var Observable_2 = Observable.Subject; - var TextMessageFormat_1 = createCommonjsModule(function (module, exports) { Object.defineProperty(exports, "__esModule", { value: true }); var TextMessageFormat = /** @class */ (function () { @@ -2096,14 +2225,15 @@ return "" + output + TextMessageFormat.RecordSeparator; }; TextMessageFormat.parse = function (input) { - if (input[input.length - 1] != TextMessageFormat.RecordSeparator) { + if (input[input.length - 1] !== TextMessageFormat.RecordSeparator) { throw new Error("Message is incomplete."); } var messages = input.split(TextMessageFormat.RecordSeparator); messages.pop(); return messages; }; - TextMessageFormat.RecordSeparator = String.fromCharCode(0x1e); + TextMessageFormat.RecordSeparatorCode = 0x1e; + TextMessageFormat.RecordSeparator = String.fromCharCode(TextMessageFormat.RecordSeparatorCode); return TextMessageFormat; }()); exports.TextMessageFormat = TextMessageFormat; @@ -2116,27 +2246,86 @@ var JsonHubProtocol_1 = createCommonjsModule(function (module, exports) { Object.defineProperty(exports, "__esModule", { value: true }); + + + exports.JSON_HUB_PROTOCOL_NAME = "json"; var JsonHubProtocol = /** @class */ (function () { function JsonHubProtocol() { this.name = exports.JSON_HUB_PROTOCOL_NAME; - this.type = 1 /* Text */; + this.version = 1; + this.transferFormat = Transports.TransferFormat.Text; } - JsonHubProtocol.prototype.parseMessages = function (input) { + JsonHubProtocol.prototype.parseMessages = function (input, logger) { if (!input) { return []; } + if (logger === null) { + logger = new Loggers.NullLogger(); + } // Parse the messages var messages = TextMessageFormat_1.TextMessageFormat.parse(input); var hubMessages = []; - for (var i = 0; i < messages.length; ++i) { - hubMessages.push(JSON.parse(messages[i])); + for (var _i = 0, messages_1 = messages; _i < messages_1.length; _i++) { + var message = messages_1[_i]; + var parsedMessage = JSON.parse(message); + if (typeof parsedMessage.type !== "number") { + throw new Error("Invalid payload."); + } + switch (parsedMessage.type) { + case 1 /* Invocation */: + this.isInvocationMessage(parsedMessage); + break; + case 2 /* StreamItem */: + this.isStreamItemMessage(parsedMessage); + break; + case 3 /* Completion */: + this.isCompletionMessage(parsedMessage); + break; + case 6 /* Ping */: + // Single value, no need to validate + break; + case 7 /* Close */: + // All optional values, no need to validate + break; + default: + // Future protocol changes can add message types, old clients can ignore them + logger.log(ILogger.LogLevel.Information, "Unknown message type '" + parsedMessage.type + "' ignored."); + continue; + } + hubMessages.push(parsedMessage); } return hubMessages; }; JsonHubProtocol.prototype.writeMessage = function (message) { return TextMessageFormat_1.TextMessageFormat.write(JSON.stringify(message)); }; + JsonHubProtocol.prototype.isInvocationMessage = function (message) { + this.assertNotEmptyString(message.target, "Invalid payload for Invocation message."); + if (message.invocationId !== undefined) { + this.assertNotEmptyString(message.invocationId, "Invalid payload for Invocation message."); + } + }; + JsonHubProtocol.prototype.isStreamItemMessage = function (message) { + this.assertNotEmptyString(message.invocationId, "Invalid payload for StreamItem message."); + if (message.item === undefined) { + throw new Error("Invalid payload for StreamItem message."); + } + }; + JsonHubProtocol.prototype.isCompletionMessage = function (message) { + if (message.result && message.error) { + throw new Error("Invalid payload for Completion message."); + } + if (!message.result && message.error) { + this.assertNotEmptyString(message.error, "Invalid payload for Completion message."); + } + this.assertNotEmptyString(message.invocationId, "Invalid payload for Completion message."); + }; + JsonHubProtocol.prototype.assertNotEmptyString = function (value, errorMessage) { + if (typeof value !== "string" || value === "") { + throw new Error(errorMessage); + } + }; return JsonHubProtocol; }()); exports.JsonHubProtocol = JsonHubProtocol; @@ -2147,58 +2336,65 @@ var JsonHubProtocol_2 = JsonHubProtocol_1.JSON_HUB_PROTOCOL_NAME; var JsonHubProtocol_3 = JsonHubProtocol_1.JsonHubProtocol; - var Base64EncodedHubProtocol_1 = createCommonjsModule(function (module, exports) { + var Observable = createCommonjsModule(function (module, exports) { Object.defineProperty(exports, "__esModule", { value: true }); - var Base64EncodedHubProtocol = /** @class */ (function () { - function Base64EncodedHubProtocol(protocol) { - this.wrappedProtocol = protocol; - this.name = this.wrappedProtocol.name; - this.type = 1 /* Text */; - } - Base64EncodedHubProtocol.prototype.parseMessages = function (input) { - // The format of the message is `size:message;` - var pos = input.indexOf(":"); - if (pos == -1 || input[input.length - 1] != ';') { - throw new Error("Invalid payload."); - } - var lenStr = input.substring(0, pos); - if (!/^[0-9]+$/.test(lenStr)) { - throw new Error("Invalid length: '" + lenStr + "'"); - } - var messageSize = parseInt(lenStr, 10); - // 2 accounts for ':' after message size and trailing ';' - if (messageSize != input.length - pos - 2) { - throw new Error("Invalid message size."); - } - var encodedMessage = input.substring(pos + 1, input.length - 1); - // atob/btoa are browsers APIs but they can be polyfilled. If this becomes problematic we can use - // base64-js module - var s = atob(encodedMessage); - var payload = new Uint8Array(s.length); - for (var i = 0; i < payload.length; i++) { - payload[i] = s.charCodeAt(i); - } - return this.wrappedProtocol.parseMessages(payload.buffer); - }; - Base64EncodedHubProtocol.prototype.writeMessage = function (message) { - var payload = new Uint8Array(this.wrappedProtocol.writeMessage(message)); - var s = ""; - for (var i = 0; i < payload.byteLength; i++) { - s += String.fromCharCode(payload[i]); - } - // atob/btoa are browsers APIs but they can be polyfilled. If this becomes problematic we can use - // base64-js module - var encodedMessage = btoa(s); - return encodedMessage.length.toString() + ":" + encodedMessage + ";"; - }; - return Base64EncodedHubProtocol; + var Subscription = /** @class */ (function () { + function Subscription(subject, observer) { + this.subject = subject; + this.observer = observer; + } + Subscription.prototype.dispose = function () { + var index = this.subject.observers.indexOf(this.observer); + if (index > -1) { + this.subject.observers.splice(index, 1); + } + if (this.subject.observers.length === 0) { + this.subject.cancelCallback().catch(function (_) { }); + } + }; + return Subscription; + }()); + exports.Subscription = Subscription; + var Subject = /** @class */ (function () { + function Subject(cancelCallback) { + this.observers = []; + this.cancelCallback = cancelCallback; + } + Subject.prototype.next = function (item) { + for (var _i = 0, _a = this.observers; _i < _a.length; _i++) { + var observer = _a[_i]; + observer.next(item); + } + }; + Subject.prototype.error = function (err) { + for (var _i = 0, _a = this.observers; _i < _a.length; _i++) { + var observer = _a[_i]; + if (observer.error) { + observer.error(err); + } + } + }; + Subject.prototype.complete = function () { + for (var _i = 0, _a = this.observers; _i < _a.length; _i++) { + var observer = _a[_i]; + if (observer.complete) { + observer.complete(); + } + } + }; + Subject.prototype.subscribe = function (observer) { + this.observers.push(observer); + return new Subscription(this, observer); + }; + return Subject; }()); - exports.Base64EncodedHubProtocol = Base64EncodedHubProtocol; + exports.Subject = Subject; }); - unwrapExports(Base64EncodedHubProtocol_1); - var Base64EncodedHubProtocol_2 = Base64EncodedHubProtocol_1.Base64EncodedHubProtocol; + unwrapExports(Observable); + var Observable_1 = Observable.Subscription; + var Observable_2 = Observable.Subject; var HubConnection_1 = createCommonjsModule(function (module, exports) { var __awaiter = (commonjsGlobal && commonjsGlobal.__awaiter) || function (thisArg, _arguments, P, generator) { @@ -2244,7 +2440,6 @@ - var DEFAULT_TIMEOUT_IN_MS = 30 * 1000; var HubConnection = /** @class */ (function () { function HubConnection(urlOrConnection, options) { @@ -2252,6 +2447,7 @@ var _this = this; options = options || {}; this.timeoutInMilliseconds = options.timeoutInMilliseconds || DEFAULT_TIMEOUT_IN_MS; + this.protocol = options.protocol || new JsonHubProtocol_1.JsonHubProtocol(); if (typeof urlOrConnection === "string") { this.connection = new HttpConnection_1.HttpConnection(urlOrConnection, options); } @@ -2259,46 +2455,107 @@ this.connection = urlOrConnection; } this.logger = Loggers.LoggerFactory.createLogger(options.logger); - this.protocol = options.protocol || new JsonHubProtocol_1.JsonHubProtocol(); this.connection.onreceive = function (data) { return _this.processIncomingData(data); }; this.connection.onclose = function (error) { return _this.connectionClosed(error); }; - this.callbacks = new Map(); - this.methods = new Map(); + this.callbacks = {}; + this.methods = {}; this.closedCallbacks = []; this.id = 0; } HubConnection.prototype.processIncomingData = function (data) { - if (this.timeoutHandle !== undefined) { - clearTimeout(this.timeoutHandle); - } - // Parse the messages - var messages = this.protocol.parseMessages(data); - for (var i = 0; i < messages.length; ++i) { - var message = messages[i]; - switch (message.type) { - case 1 /* Invocation */: - this.invokeClientMethod(message); - break; - case 2 /* StreamItem */: - case 3 /* Completion */: - var callback = this.callbacks.get(message.invocationId); - if (callback != null) { - if (message.type === 3 /* Completion */) { - this.callbacks.delete(message.invocationId); + this.cleanupTimeout(); + if (!this.receivedHandshakeResponse) { + data = this.processHandshakeResponse(data); + this.receivedHandshakeResponse = true; + } + // Data may have all been read when processing handshake response + if (data) { + // Parse the messages + var messages = this.protocol.parseMessages(data, this.logger); + for (var _i = 0, messages_1 = messages; _i < messages_1.length; _i++) { + var message = messages_1[_i]; + switch (message.type) { + case 1 /* Invocation */: + this.invokeClientMethod(message); + break; + case 2 /* StreamItem */: + case 3 /* Completion */: + var callback = this.callbacks[message.invocationId]; + if (callback != null) { + if (message.type === 3 /* Completion */) { + delete this.callbacks[message.invocationId]; + } + callback(message); } - callback(message); - } - break; - case 6 /* Ping */: - // Don't care about pings - break; - default: - this.logger.log(ILogger.LogLevel.Warning, "Invalid message type: " + data); - break; + break; + case 6 /* Ping */: + // Don't care about pings + break; + case 7 /* Close */: + this.logger.log(ILogger.LogLevel.Information, "Close message received from server."); + this.connection.stop(message.error ? new Error("Server returned an error on close: " + message.error) : null); + break; + default: + this.logger.log(ILogger.LogLevel.Warning, "Invalid message type: " + message.type); + break; + } } } this.configureTimeout(); }; + HubConnection.prototype.processHandshakeResponse = function (data) { + var responseMessage; + var messageData; + var remainingData; + try { + if (data instanceof ArrayBuffer) { + // Format is binary but still need to read JSON text from handshake response + var binaryData = new Uint8Array(data); + var separatorIndex = binaryData.indexOf(TextMessageFormat_1.TextMessageFormat.RecordSeparatorCode); + if (separatorIndex === -1) { + throw new Error("Message is incomplete."); + } + // content before separator is handshake response + // optional content after is additional messages + var responseLength = separatorIndex + 1; + messageData = String.fromCharCode.apply(null, binaryData.slice(0, responseLength)); + remainingData = (binaryData.byteLength > responseLength) ? binaryData.slice(responseLength).buffer : null; + } + else { + var textData = data; + var separatorIndex = textData.indexOf(TextMessageFormat_1.TextMessageFormat.RecordSeparator); + if (separatorIndex === -1) { + throw new Error("Message is incomplete."); + } + // content before separator is handshake response + // optional content after is additional messages + var responseLength = separatorIndex + 1; + messageData = textData.substring(0, responseLength); + remainingData = (textData.length > responseLength) ? textData.substring(responseLength) : null; + } + // At this point we should have just the single handshake message + var messages = TextMessageFormat_1.TextMessageFormat.parse(messageData); + responseMessage = JSON.parse(messages[0]); + } + catch (e) { + var message = "Error parsing handshake response: " + e; + this.logger.log(ILogger.LogLevel.Error, message); + var error = new Error(message); + this.connection.stop(error); + throw error; + } + if (responseMessage.error) { + var message = "Server returned handshake error: " + responseMessage.error; + this.logger.log(ILogger.LogLevel.Error, message); + this.connection.stop(new Error(message)); + } + else { + this.logger.log(ILogger.LogLevel.Trace, "Server handshake complete."); + } + // multiple messages could have arrived with handshake + // return additional data to be parsed as usual, or null if all parsed + return remainingData; + }; HubConnection.prototype.configureTimeout = function () { var _this = this; if (!this.connection.features || !this.connection.features.inherentKeepAlive) { @@ -2313,7 +2570,7 @@ }; HubConnection.prototype.invokeClientMethod = function (invocationMessage) { var _this = this; - var methods = this.methods.get(invocationMessage.target.toLowerCase()); + var methods = this.methods[invocationMessage.target.toLowerCase()]; if (methods) { methods.forEach(function (m) { return m.apply(_this, invocationMessage.arguments); }); if (invocationMessage.invocationId) { @@ -2329,34 +2586,35 @@ }; HubConnection.prototype.connectionClosed = function (error) { var _this = this; - this.callbacks.forEach(function (callback) { - callback(undefined, error ? error : new Error("Invocation canceled due to connection being closed.")); - }); - this.callbacks.clear(); - this.closedCallbacks.forEach(function (c) { return c.apply(_this, [error]); }); + var callbacks = this.callbacks; + this.callbacks = {}; + Object.keys(callbacks) + .forEach(function (key) { + var callback = callbacks[key]; + callback(undefined, error ? error : new Error("Invocation canceled due to connection being closed.")); + }); this.cleanupTimeout(); + this.closedCallbacks.forEach(function (c) { return c.apply(_this, [error]); }); }; HubConnection.prototype.start = function () { return __awaiter(this, void 0, void 0, function () { - var requestedTransferMode, actualTransferMode; return __generator(this, function (_a) { switch (_a.label) { case 0: - requestedTransferMode = (this.protocol.type === 2 /* Binary */) - ? 2 /* Binary */ - : 1 /* Text */; - this.connection.features.transferMode = requestedTransferMode; - return [4 /*yield*/, this.connection.start()]; + this.logger.log(ILogger.LogLevel.Trace, "Starting HubConnection."); + this.receivedHandshakeResponse = false; + return [4 /*yield*/, this.connection.start(this.protocol.transferFormat)]; case 1: _a.sent(); - actualTransferMode = this.connection.features.transferMode; - return [4 /*yield*/, this.connection.send(TextMessageFormat_1.TextMessageFormat.write(JSON.stringify({ protocol: this.protocol.name })))]; + this.logger.log(ILogger.LogLevel.Trace, "Sending handshake request."); + // Handshake request is always JSON + return [4 /*yield*/, this.connection.send(TextMessageFormat_1.TextMessageFormat.write(JSON.stringify({ protocol: this.protocol.name, version: this.protocol.version })))]; case 2: + // Handshake request is always JSON _a.sent(); this.logger.log(ILogger.LogLevel.Information, "Using HubProtocol '" + this.protocol.name + "'."); - if (requestedTransferMode === 2 /* Binary */ && actualTransferMode === 1 /* Text */) { - this.protocol = new Base64EncodedHubProtocol_1.Base64EncodedHubProtocol(this.protocol); - } + // defensively cleanup timeout in case we receive a message from the server before we finish start + this.cleanupTimeout(); this.configureTimeout(); return [2 /*return*/]; } @@ -2364,6 +2622,7 @@ }); }; HubConnection.prototype.stop = function () { + this.logger.log(ILogger.LogLevel.Trace, "Stopping HubConnection."); this.cleanupTimeout(); return this.connection.stop(); }; @@ -2376,33 +2635,32 @@ var invocationDescriptor = this.createStreamInvocation(methodName, args); var subject = new Observable.Subject(function () { var cancelInvocation = _this.createCancelInvocation(invocationDescriptor.invocationId); - var message = _this.protocol.writeMessage(cancelInvocation); - _this.callbacks.delete(invocationDescriptor.invocationId); - return _this.connection.send(message); + var cancelMessage = _this.protocol.writeMessage(cancelInvocation); + delete _this.callbacks[invocationDescriptor.invocationId]; + return _this.connection.send(cancelMessage); }); - this.callbacks.set(invocationDescriptor.invocationId, function (invocationEvent, error) { + this.callbacks[invocationDescriptor.invocationId] = function (invocationEvent, error) { if (error) { subject.error(error); return; } if (invocationEvent.type === 3 /* Completion */) { - var completionMessage = invocationEvent; - if (completionMessage.error) { - subject.error(new Error(completionMessage.error)); + if (invocationEvent.error) { + subject.error(new Error(invocationEvent.error)); } else { subject.complete(); } } else { - subject.next(invocationEvent.item); + subject.next((invocationEvent.item)); } - }); + }; var message = this.protocol.writeMessage(invocationDescriptor); this.connection.send(message) .catch(function (e) { subject.error(e); - _this.callbacks.delete(invocationDescriptor.invocationId); + delete _this.callbacks[invocationDescriptor.invocationId]; }); return subject; }; @@ -2423,7 +2681,7 @@ } var invocationDescriptor = this.createInvocation(methodName, args, false); var p = new Promise(function (resolve, reject) { - _this.callbacks.set(invocationDescriptor.invocationId, function (invocationEvent, error) { + _this.callbacks[invocationDescriptor.invocationId] = function (invocationEvent, error) { if (error) { reject(error); return; @@ -2440,38 +2698,50 @@ else { reject(new Error("Unexpected message type: " + invocationEvent.type)); } - }); + }; var message = _this.protocol.writeMessage(invocationDescriptor); _this.connection.send(message) .catch(function (e) { reject(e); - _this.callbacks.delete(invocationDescriptor.invocationId); + delete _this.callbacks[invocationDescriptor.invocationId]; }); }); return p; }; - HubConnection.prototype.on = function (methodName, method) { - if (!methodName || !method) { + HubConnection.prototype.on = function (methodName, newMethod) { + if (!methodName || !newMethod) { return; } methodName = methodName.toLowerCase(); - if (!this.methods.has(methodName)) { - this.methods.set(methodName, []); + if (!this.methods[methodName]) { + this.methods[methodName] = []; } - this.methods.get(methodName).push(method); + // Preventing adding the same handler multiple times. + if (this.methods[methodName].indexOf(newMethod) !== -1) { + return; + } + this.methods[methodName].push(newMethod); }; HubConnection.prototype.off = function (methodName, method) { - if (!methodName || !method) { + if (!methodName) { return; } methodName = methodName.toLowerCase(); - var handlers = this.methods.get(methodName); + var handlers = this.methods[methodName]; if (!handlers) { return; } - var removeIdx = handlers.indexOf(method); - if (removeIdx != -1) { - handlers.splice(removeIdx, 1); + if (method) { + var removeIdx = handlers.indexOf(method); + if (removeIdx !== -1) { + handlers.splice(removeIdx, 1); + if (handlers.length === 0) { + delete this.methods[methodName]; + } + } + } + else { + delete this.methods[methodName]; } }; HubConnection.prototype.onclose = function (callback) { @@ -2487,19 +2757,19 @@ HubConnection.prototype.createInvocation = function (methodName, args, nonblocking) { if (nonblocking) { return { - type: 1 /* Invocation */, - target: methodName, arguments: args, + target: methodName, + type: 1 /* Invocation */, }; } else { var id = this.id; this.id++; return { - type: 1 /* Invocation */, + arguments: args, invocationId: id.toString(), target: methodName, - arguments: args, + type: 1 /* Invocation */, }; } }; @@ -2507,16 +2777,16 @@ var id = this.id; this.id++; return { - type: 4 /* StreamInvocation */, + arguments: args, invocationId: id.toString(), target: methodName, - arguments: args, + type: 4 /* StreamInvocation */, }; }; HubConnection.prototype.createCancelInvocation = function (id) { return { - type: 5 /* CancelInvocation */, invocationId: id, + type: 5 /* CancelInvocation */, }; }; return HubConnection; diff --git a/ASP.NET Demo/WebApplication.Knockout/Scripts/Demo/BetterList.js b/ASP.NET Demo/WebApplication.Knockout/Scripts/Demo/BetterList.js index f463f52e..8441f0a1 100644 --- a/ASP.NET Demo/WebApplication.Knockout/Scripts/Demo/BetterList.js +++ b/ASP.NET Demo/WebApplication.Knockout/Scripts/Demo/BetterList.js @@ -1,4 +1,4 @@ -var BetterListVM = (function () { +var BetterListVM = /** @class */ (function () { function BetterListVM() { this._firstName = ""; this._lastName = ""; diff --git a/ASP.NET Demo/WebApplication.Knockout/Scripts/Demo/CompositeView.js b/ASP.NET Demo/WebApplication.Knockout/Scripts/Demo/CompositeView.js index c4548069..8abc3e46 100644 --- a/ASP.NET Demo/WebApplication.Knockout/Scripts/Demo/CompositeView.js +++ b/ASP.NET Demo/WebApplication.Knockout/Scripts/Demo/CompositeView.js @@ -8,7 +8,7 @@ var __extends = (this && this.__extends) || (function () { d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); -var LinkedTreeViewVM = (function (_super) { +var LinkedTreeViewVM = /** @class */ (function (_super) { __extends(LinkedTreeViewVM, _super); function LinkedTreeViewVM() { return _super !== null && _super.apply(this, arguments) || this; diff --git a/ASP.NET Demo/WebApplication.Knockout/Scripts/Demo/Dashboard.js b/ASP.NET Demo/WebApplication.Knockout/Scripts/Demo/Dashboard.js index 5934b676..5404a163 100644 --- a/ASP.NET Demo/WebApplication.Knockout/Scripts/Demo/Dashboard.js +++ b/ASP.NET Demo/WebApplication.Knockout/Scripts/Demo/Dashboard.js @@ -1,4 +1,4 @@ -var DashboardPanelVM = (function () { +var DashboardPanelVM = /** @class */ (function () { function DashboardPanelVM() { } DashboardPanelVM.prototype.clearAll = function () { $("#DisplayArea").empty(); }; diff --git a/ASP.NET Demo/WebApplication.Knockout/Scripts/Demo/GridView.js b/ASP.NET Demo/WebApplication.Knockout/Scripts/Demo/GridView.js index 685e231b..d7c159af 100644 --- a/ASP.NET Demo/WebApplication.Knockout/Scripts/Demo/GridView.js +++ b/ASP.NET Demo/WebApplication.Knockout/Scripts/Demo/GridView.js @@ -1,4 +1,4 @@ -var GridViewVM = (function () { +var GridViewVM = /** @class */ (function () { function GridViewVM() { } // Inject local observables into each ListHeader's item to implement sorting. diff --git a/ASP.NET Demo/WebApplication.Knockout/Scripts/Demo/JobQueue.js b/ASP.NET Demo/WebApplication.Knockout/Scripts/Demo/JobQueue.js index 1db1eb13..31e7924c 100644 --- a/ASP.NET Demo/WebApplication.Knockout/Scripts/Demo/JobQueue.js +++ b/ASP.NET Demo/WebApplication.Knockout/Scripts/Demo/JobQueue.js @@ -1,4 +1,4 @@ -var JobQueueVM = (function () { +var JobQueueVM = /** @class */ (function () { function JobQueueVM() { this._jobID = ""; this._jobOutput = ""; diff --git a/ASP.NET Demo/WebApplication.Knockout/Scripts/Demo/LiveChart.js b/ASP.NET Demo/WebApplication.Knockout/Scripts/Demo/LiveChart.js index 6849e7d2..1d5e17ad 100644 --- a/ASP.NET Demo/WebApplication.Knockout/Scripts/Demo/LiveChart.js +++ b/ASP.NET Demo/WebApplication.Knockout/Scripts/Demo/LiveChart.js @@ -1,4 +1,4 @@ -var LiveChartVM = (function () { +var LiveChartVM = /** @class */ (function () { function LiveChartVM() { } LiveChartVM.prototype.updateChart = function (iItem, iElement) { diff --git a/ASP.NET Demo/WebApplication.Knockout/Scripts/Demo/TreeView.js b/ASP.NET Demo/WebApplication.Knockout/Scripts/Demo/TreeView.js index 56fd35be..3e1e2532 100644 --- a/ASP.NET Demo/WebApplication.Knockout/Scripts/Demo/TreeView.js +++ b/ASP.NET Demo/WebApplication.Knockout/Scripts/Demo/TreeView.js @@ -1,4 +1,4 @@ -var TreeViewVM = (function () { +var TreeViewVM = /** @class */ (function () { function TreeViewVM() { } TreeViewVM.prototype.isSelected = function (iItem) { return this.SelectedId() == iItem.Id(); }; diff --git a/ASP.NET Demo/WebApplication.Knockout/Scripts/dotnetify.js b/ASP.NET Demo/WebApplication.Knockout/Scripts/dotnetify.js index 466f4f9e..77e6467f 100644 --- a/ASP.NET Demo/WebApplication.Knockout/Scripts/dotnetify.js +++ b/ASP.NET Demo/WebApplication.Knockout/Scripts/dotnetify.js @@ -35,7 +35,7 @@ var dotNetify = {}; dotnetify = $.extend(dotnetify, { - version: "1.1.5", + version: "1.2.0", // SignalR hub options. hub: dotnetifyHub, @@ -65,6 +65,12 @@ var dotNetify = {}; dotnetifyHub.client.response_VM = function (iVMId, iVMData) { + // SignalR .NET Core is sending an array of arguments. + if (Array.isArray(iVMId)) { + iVMData = iVMId[1]; + iVMId = iVMId[0]; + } + // Report unauthorized access. if (iVMData == "403") { console.error("Unauthorized access to " + iVMId); diff --git a/ASP.NET Demo/WebApplication.Knockout/Scripts/dotnetify.router.js b/ASP.NET Demo/WebApplication.Knockout/Scripts/dotnetify.router.js index a75c27bc..98b362e7 100644 --- a/ASP.NET Demo/WebApplication.Knockout/Scripts/dotnetify.router.js +++ b/ASP.NET Demo/WebApplication.Knockout/Scripts/dotnetify.router.js @@ -34,7 +34,7 @@ limitations under the License. // Add plugin functions. dotnetify.router = { - version: "1.1.3", + version: "1.2.0", // URL path that will be parsed when performing routing. urlPath: document.location.pathname, diff --git a/ASP.NET Demo/WebApplication.Knockout/WebApplication.csproj b/ASP.NET Demo/WebApplication.Knockout/WebApplication.csproj index 8dfbc63c..008d2af9 100644 --- a/ASP.NET Demo/WebApplication.Knockout/WebApplication.csproj +++ b/ASP.NET Demo/WebApplication.Knockout/WebApplication.csproj @@ -28,7 +28,7 @@ - 2.3 + 2.6 diff --git a/DotNetifyLib.Core/DotNetifyLib.Core.csproj b/DotNetifyLib.Core/DotNetifyLib.Core.csproj index 55772a25..d942a1ee 100644 --- a/DotNetifyLib.Core/DotNetifyLib.Core.csproj +++ b/DotNetifyLib.Core/DotNetifyLib.Core.csproj @@ -9,7 +9,7 @@ false false false - 2.3.2-pre + 3.0.0-pre True Simple, lightweight, yet powerful way to build reactive, real-time web apps. Library for .NET Core. Dicky Suryadi diff --git a/DotNetifyLib.SignalR.Owin/Properties/AssemblyInfo.cs b/DotNetifyLib.SignalR.Owin/Properties/AssemblyInfo.cs index 92e2bfae..b9ef90a2 100644 --- a/DotNetifyLib.SignalR.Owin/Properties/AssemblyInfo.cs +++ b/DotNetifyLib.SignalR.Owin/Properties/AssemblyInfo.cs @@ -1,8 +1,7 @@ using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -// General Information about an assembly is controlled through the following +// General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("DotNetifyLib.SignalR.Owin")] @@ -10,12 +9,12 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("DotNetify")] -[assembly: AssemblyCopyright("Copyright © 2017")] +[assembly: AssemblyCopyright("Copyright © 2015-2018")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] @@ -25,12 +24,12 @@ // Version information for an assembly consists of the following four values: // // Major Version -// Minor Version +// Minor Version // Build Number // Revision // -// You can specify all the values or you can default the Build and Revision Numbers +// You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("2.3.2.0")] -[assembly: AssemblyFileVersion("2.3.2.0")] +[assembly: AssemblyVersion("3.0.0.0")] +[assembly: AssemblyFileVersion("3.0.0.0")] \ No newline at end of file diff --git a/DotNetifyLib.SignalR/DotNetifyLib.SignalR.csproj b/DotNetifyLib.SignalR/DotNetifyLib.SignalR.csproj index 72605601..64da0a51 100644 --- a/DotNetifyLib.SignalR/DotNetifyLib.SignalR.csproj +++ b/DotNetifyLib.SignalR/DotNetifyLib.SignalR.csproj @@ -9,7 +9,7 @@ false false false - 2.3.2-pre + 3.0.0-pre True Dicky Suryadi Simple, lightweight, yet powerful way to build reactive, real-time web apps. SignalR hub for ASP.NET Core. @@ -25,10 +25,10 @@ - + - + diff --git a/dist/dotnetify-react.js b/dist/dotnetify-react.js index bbaeeae4..406f4147 100644 --- a/dist/dotnetify-react.js +++ b/dist/dotnetify-react.js @@ -69,7 +69,7 @@ var dotnetify = typeof dotnetify === "undefined" ? {} : dotnetify; }); dotnetify.react = $.extend(dotnetify.hasOwnProperty("react") ? dotnetify.react : {}, { - version: "1.0.6-beta", + version: "1.1.0", viewModels: {}, plugins: {}, @@ -83,6 +83,12 @@ var dotnetify = typeof dotnetify === "undefined" ? {} : dotnetify; // Setup SignalR server method handler. dotnetifyHub.client.response_VM = function (iVMId, iVMData) { + // SignalR .NET Core is sending an array of arguments. + if (Array.isArray(iVMId)) { + iVMData = iVMId[1]; + iVMId = iVMId[0]; + } + var vm = self.viewModels.hasOwnProperty(iVMId) ? self.viewModels[iVMId] : null; // Handle server-side exception. diff --git a/dist/dotnetify-react.min.js b/dist/dotnetify-react.min.js index 25a48a5b..7af79f2f 100644 --- a/dist/dotnetify-react.min.js +++ b/dist/dotnetify-react.min.js @@ -1 +1 @@ -var dotnetifyHub="undefined"==typeof dotnetifyHub?{}:dotnetifyHub;!function(a){var b=window||global;if("object"==typeof exports&&"object"==typeof module){var c=window?window.jQuery||require("./jquery-shim"):require("./jquery-shim"),d=window?window.signalR||require("./signalR-netcore"):require("./signalR-netcore");module.exports=a(c,d,b)}else"function"==typeof define&&define.amd?define(["jquery","signalR"],a):a(jQuery,b.signalR,b)}(function(a,b,c){return dotnetifyHub=a.extend(dotnetifyHub,{version:"1.1.1-beta",type:null,_init:!1}),dotnetifyHub.init=function(d,e,f){dotnetifyHub._init||(dotnetifyHub._init=!0,f=f||b,f&&f.HubConnection?(dotnetifyHub.type="netcore",Object.defineProperty(dotnetifyHub,"isConnected",{get:function(){return dotnetifyHub._connection&&1===dotnetifyHub._connection.connection.connectionState}}),dotnetifyHub=a.extend(dotnetifyHub,{hubPath:d||"/dotnetify",url:e,reconnectDelay:[2,5,10],reconnectRetry:null,_connection:null,_reconnectCount:0,_startDoneHandler:null,_startFailHandler:null,_disconnectedHandler:function(){},_stateChangedHandler:function(a){},_onDisconnected:function(){dotnetifyHub._changeState(4),dotnetifyHub._disconnectedHandler()},_changeState:function(a){1==a&&(dotnetifyHub._reconnectCount=0);var b={0:"connecting",1:"connected",2:"reconnecting",4:"disconnected",99:"terminated"};dotnetifyHub._stateChangedHandler(b[a])},_startConnection:function(a,b){var c=dotnetifyHub.url?dotnetifyHub.url+dotnetifyHub.hubPath:dotnetifyHub.hubPath,d={};Object.keys(a).forEach(function(b){d[b]=a[b]}),d.transport=b.shift(),dotnetifyHub._connection=new f.HubConnection(c,d),dotnetifyHub._connection.on("response_vm",dotnetifyHub.client.response_VM),dotnetifyHub._connection.onclose(dotnetifyHub._onDisconnected);var e=dotnetifyHub._connection.start().then(function(){dotnetifyHub._changeState(1)}).catch(function(){b.length>0?dotnetifyHub._startConnection(a,b):dotnetifyHub._onDisconnected()});return"function"==typeof dotnetifyHub._startDoneHandler&&e.then(dotnetifyHub._startDoneHandler).catch(dotnetifyHub._startFailHandler||function(){}),e},start:function(a){dotnetifyHub._startDoneHandler=null,dotnetifyHub._startFailHandler=null;var b=[0],c={webSockets:0,serverSentEvents:1,longPolling:2};a&&Array.isArray(a.transport)&&(b=a.transport.map(function(a){return c[a]}));var d=dotnetifyHub._startConnection(a,b);return{done:function(a){return dotnetifyHub._startDoneHandler=a,d.then(a).catch(function(){}),this},fail:function(a){return dotnetifyHub._startFailHandler=a,d.catch(a),this}}},disconnected:function(a){"function"==typeof a&&(dotnetifyHub._disconnectedHandler=a)},stateChanged:function(a){"function"==typeof a&&(dotnetifyHub._stateChangedHandler=a)},reconnect:function(a){if("function"==typeof a)if(!dotnetifyHub.reconnectRetry||dotnetifyHub._reconnectCount3)throw new Error("[dotNetify] Deprecated parameters. New usage: connect(vmId, component [,{getState, setState, vmArg, headers, exceptionHandler}]) ");if(dotnetify.ssr){var e=c&&c.vmArg;return dotnetify.react.ssrConnect(a,b,e)}var f=dotnetify.react;return f.viewModels.hasOwnProperty(a)?console.error("Component is attempting to connect to an already active '"+a+"'. If it's from a dismounted component, you must add vm.$destroy to componentWillUnmount()."):f.viewModels[a]=new d(a,b,c),f.init(),f.viewModels[a]},ssrConnect:function(c,e,f){null!=b.ssr&&b.ssr.hasOwnProperty(c)||console.error("Server-side rendering requires initial state in 'window.ssr."+c+"'.");var g=dotnetify.react,h=b.ssr[c],i=function(){return h},j=function(b){h=a.extend(h,b)},k={getState:i,setState:j,vmArg:f},l=g.viewModels[c]=new d(c,e,k);return setTimeout(function(){l.$update(JSON.stringify(b.ssr[c]))},100),l},getViewModels:function(){var a=dotnetify.react,b=[];for(var c in a.viewModels)b.push(a.viewModels[c]);return b}}),d.prototype.$destroy=function(){for(var a in dotnetify.react.plugins){var b=dotnetify.react.plugins[a];"function"==typeof b.$destroy&&b.$destroy.apply(this)}if(dotnetify.isConnected())try{c.server.dispose_VM(this.$vmId)}catch(a){dotnetify._triggerConnectionStateEvent("error",a)}delete dotnetify.react.viewModels[this.$vmId]},d.prototype.$dispatch=function(a){if(dotnetify.isConnected())try{c.server.update_VM(this.$vmId,a),dotnetify.debug&&(console.log("["+this.$vmId+"] sent> "),console.log(a),null!=dotnetify.debugFn&&dotnetify.debugFn(this.$vmId,"sent",a))}catch(a){dotnetify._triggerConnectionStateEvent("error",a)}},d.prototype.$dispatchListState=function(a){for(var b in a){var c=this.$itemKey[b];if(!c)return void console.error("["+this.$vmId+"] missing item key for '"+b+"'; add "+b+"_itemKey property to the view model.");var d=a[b];if(!d[c])return console.error("["+this.$vmId+"] couldn't dispatch data from '"+b+"' due to missing property '"+c+"'"),void console.error(d);for(var e in d)if(e!=c){var f={};f[b+".$"+d[c]+"."+e]=d[e],this.$dispatch(f)}this.$updateList(b,d)}},d.prototype.$preProcess=function(a){var b=this;for(var c in a){var d=/(.*)_add/.exec(c);if(null==d){var d=/(.*)_update/.exec(c);if(null==d){var d=/(.*)_remove/.exec(c);if(null==d){var d=/(.*)_itemKey/.exec(c);if(null==d);else{var e=d[1],f={};f[e]=a[c],b.$setItemKey(f),delete a[c]}}else{var e=d[1];if(Array.isArray(this.State()[e])){var g=this.$itemKey[e];null!=g?b.$removeList(e,function(b){return b[g]==a[c]}):console.error("["+this.$vmId+"] missing item key for '"+e+"'; add "+e+"_itemKey property to the view model.")}else console.error("["+this.$vmId+"] '"+e+"' is not found or not an array.");delete a[c]}}else{var e=d[1];Array.isArray(this.State()[e])?b.$updateList(e,a[c]):console.error("["+this.$vmId+"] '"+e+"' is not found or not an array."),delete a[c]}}else{var e=d[1];Array.isArray(this.State()[e])?b.$addList(e,a[c]):console.error("unable to resolve "+c),delete a[c]}}},d.prototype.$request=function(){dotnetify.isConnected()&&(c.server.request_VM(this.$vmId,{$vmArg:this.$vmArg,$headers:this.$headers}),this.$requested=!0)},d.prototype.$update=function(b){dotnetify.debug&&(console.log("["+this.$vmId+"] received> "),console.log(JSON.parse(b)),null!=dotnetify.debugFn&&dotnetify.debugFn(this.$vmId,"received",JSON.parse(b)));var c=JSON.parse(b);this.$preProcess(c);var d=this.State();d=a.extend({},d,c),this.State(d),this.$loaded||this.$onLoad()},d.prototype.$onLoad=function(){for(var a in dotnetify.react.plugins){var b=dotnetify.react.plugins[a];"function"==typeof b.$ready&&b.$ready.apply(this)}this.$loaded=!0},d.prototype.$setItemKey=function(a){this.$itemKey=a},d.prototype.$addList=function(a,b){var c=this.$itemKey[a];if(null!=c){if(!b.hasOwnProperty(c))return void console.error("["+this.$vmId+"] couldn't add item to '"+a+"' due to missing property '"+c+"'");var d=this.State()[a].filter(function(a){return a[c]==b[c]});if(d.length>0)return void console.error("["+this.$vmId+"] couldn't add item to '"+a+"' because the key already exists")}var e=this.State()[a];e.push(b);var f={};f[a]=e,this.State(f)},d.prototype.$removeList=function(a,b){var c={};c[a]=this.State()[a].filter(function(a){return!b(a)}),this.State(c)},d.prototype.$updateList=function(b,c){var d=this.$itemKey[b];if(null!=d){if(!c.hasOwnProperty(d))return void console.error("["+this.$vmId+"] couldn't update item to '"+b+"' due to missing property '"+d+"'");var e={};e[b]=this.State()[b].map(function(b){return b[d]==c[d]?a.extend(b,c):b}),this.State(e)}else console.error("["+this.$vmId+"] missing item key for '"+listName+"'; add "+listName+"_itemKey property to the view model.")},dotnetify}); \ No newline at end of file +var dotnetifyHub="undefined"==typeof dotnetifyHub?{}:dotnetifyHub;!function(a){var b=window||global;if("object"==typeof exports&&"object"==typeof module){var c=window?window.jQuery||require("./jquery-shim"):require("./jquery-shim"),d=window?window.signalR||require("./signalR-netcore"):require("./signalR-netcore");module.exports=a(c,d,b)}else"function"==typeof define&&define.amd?define(["jquery","signalR"],a):a(jQuery,b.signalR,b)}(function(a,b,c){return dotnetifyHub=a.extend(dotnetifyHub,{version:"1.1.1-beta",type:null,_init:!1}),dotnetifyHub.init=function(d,e,f){dotnetifyHub._init||(dotnetifyHub._init=!0,f=f||b,f&&f.HubConnection?(dotnetifyHub.type="netcore",Object.defineProperty(dotnetifyHub,"isConnected",{get:function(){return dotnetifyHub._connection&&1===dotnetifyHub._connection.connection.connectionState}}),dotnetifyHub=a.extend(dotnetifyHub,{hubPath:d||"/dotnetify",url:e,reconnectDelay:[2,5,10],reconnectRetry:null,_connection:null,_reconnectCount:0,_startDoneHandler:null,_startFailHandler:null,_disconnectedHandler:function(){},_stateChangedHandler:function(a){},_onDisconnected:function(){dotnetifyHub._changeState(4),dotnetifyHub._disconnectedHandler()},_changeState:function(a){1==a&&(dotnetifyHub._reconnectCount=0);var b={0:"connecting",1:"connected",2:"reconnecting",4:"disconnected",99:"terminated"};dotnetifyHub._stateChangedHandler(b[a])},_startConnection:function(a,b){var c=dotnetifyHub.url?dotnetifyHub.url+dotnetifyHub.hubPath:dotnetifyHub.hubPath,d={};Object.keys(a).forEach(function(b){d[b]=a[b]}),d.transport=b.shift(),dotnetifyHub._connection=new f.HubConnection(c,d),dotnetifyHub._connection.on("response_vm",dotnetifyHub.client.response_VM),dotnetifyHub._connection.onclose(dotnetifyHub._onDisconnected);var e=dotnetifyHub._connection.start().then(function(){dotnetifyHub._changeState(1)}).catch(function(){b.length>0?dotnetifyHub._startConnection(a,b):dotnetifyHub._onDisconnected()});return"function"==typeof dotnetifyHub._startDoneHandler&&e.then(dotnetifyHub._startDoneHandler).catch(dotnetifyHub._startFailHandler||function(){}),e},start:function(a){dotnetifyHub._startDoneHandler=null,dotnetifyHub._startFailHandler=null;var b=[0],c={webSockets:0,serverSentEvents:1,longPolling:2};a&&Array.isArray(a.transport)&&(b=a.transport.map(function(a){return c[a]}));var d=dotnetifyHub._startConnection(a,b);return{done:function(a){return dotnetifyHub._startDoneHandler=a,d.then(a).catch(function(){}),this},fail:function(a){return dotnetifyHub._startFailHandler=a,d.catch(a),this}}},disconnected:function(a){"function"==typeof a&&(dotnetifyHub._disconnectedHandler=a)},stateChanged:function(a){"function"==typeof a&&(dotnetifyHub._stateChangedHandler=a)},reconnect:function(a){if("function"==typeof a)if(!dotnetifyHub.reconnectRetry||dotnetifyHub._reconnectCount3)throw new Error("[dotNetify] Deprecated parameters. New usage: connect(vmId, component [,{getState, setState, vmArg, headers, exceptionHandler}]) ");if(dotnetify.ssr){var e=c&&c.vmArg;return dotnetify.react.ssrConnect(a,b,e)}var f=dotnetify.react;return f.viewModels.hasOwnProperty(a)?console.error("Component is attempting to connect to an already active '"+a+"'. If it's from a dismounted component, you must add vm.$destroy to componentWillUnmount()."):f.viewModels[a]=new d(a,b,c),f.init(),f.viewModels[a]},ssrConnect:function(c,e,f){null!=b.ssr&&b.ssr.hasOwnProperty(c)||console.error("Server-side rendering requires initial state in 'window.ssr."+c+"'.");var g=dotnetify.react,h=b.ssr[c],i=function(){return h},j=function(b){h=a.extend(h,b)},k={getState:i,setState:j,vmArg:f},l=g.viewModels[c]=new d(c,e,k);return setTimeout(function(){l.$update(JSON.stringify(b.ssr[c]))},100),l},getViewModels:function(){var a=dotnetify.react,b=[];for(var c in a.viewModels)b.push(a.viewModels[c]);return b}}),d.prototype.$destroy=function(){for(var a in dotnetify.react.plugins){var b=dotnetify.react.plugins[a];"function"==typeof b.$destroy&&b.$destroy.apply(this)}if(dotnetify.isConnected())try{c.server.dispose_VM(this.$vmId)}catch(a){dotnetify._triggerConnectionStateEvent("error",a)}delete dotnetify.react.viewModels[this.$vmId]},d.prototype.$dispatch=function(a){if(dotnetify.isConnected())try{c.server.update_VM(this.$vmId,a),dotnetify.debug&&(console.log("["+this.$vmId+"] sent> "),console.log(a),null!=dotnetify.debugFn&&dotnetify.debugFn(this.$vmId,"sent",a))}catch(a){dotnetify._triggerConnectionStateEvent("error",a)}},d.prototype.$dispatchListState=function(a){for(var b in a){var c=this.$itemKey[b];if(!c)return void console.error("["+this.$vmId+"] missing item key for '"+b+"'; add "+b+"_itemKey property to the view model.");var d=a[b];if(!d[c])return console.error("["+this.$vmId+"] couldn't dispatch data from '"+b+"' due to missing property '"+c+"'"),void console.error(d);for(var e in d)if(e!=c){var f={};f[b+".$"+d[c]+"."+e]=d[e],this.$dispatch(f)}this.$updateList(b,d)}},d.prototype.$preProcess=function(a){var b=this;for(var c in a){var d=/(.*)_add/.exec(c);if(null==d){var d=/(.*)_update/.exec(c);if(null==d){var d=/(.*)_remove/.exec(c);if(null==d){var d=/(.*)_itemKey/.exec(c);if(null==d);else{var e=d[1],f={};f[e]=a[c],b.$setItemKey(f),delete a[c]}}else{var e=d[1];if(Array.isArray(this.State()[e])){var g=this.$itemKey[e];null!=g?b.$removeList(e,function(b){return b[g]==a[c]}):console.error("["+this.$vmId+"] missing item key for '"+e+"'; add "+e+"_itemKey property to the view model.")}else console.error("["+this.$vmId+"] '"+e+"' is not found or not an array.");delete a[c]}}else{var e=d[1];Array.isArray(this.State()[e])?b.$updateList(e,a[c]):console.error("["+this.$vmId+"] '"+e+"' is not found or not an array."),delete a[c]}}else{var e=d[1];Array.isArray(this.State()[e])?b.$addList(e,a[c]):console.error("unable to resolve "+c),delete a[c]}}},d.prototype.$request=function(){dotnetify.isConnected()&&(c.server.request_VM(this.$vmId,{$vmArg:this.$vmArg,$headers:this.$headers}),this.$requested=!0)},d.prototype.$update=function(b){dotnetify.debug&&(console.log("["+this.$vmId+"] received> "),console.log(JSON.parse(b)),null!=dotnetify.debugFn&&dotnetify.debugFn(this.$vmId,"received",JSON.parse(b)));var c=JSON.parse(b);this.$preProcess(c);var d=this.State();d=a.extend({},d,c),this.State(d),this.$loaded||this.$onLoad()},d.prototype.$onLoad=function(){for(var a in dotnetify.react.plugins){var b=dotnetify.react.plugins[a];"function"==typeof b.$ready&&b.$ready.apply(this)}this.$loaded=!0},d.prototype.$setItemKey=function(a){this.$itemKey=a},d.prototype.$addList=function(a,b){var c=this.$itemKey[a];if(null!=c){if(!b.hasOwnProperty(c))return void console.error("["+this.$vmId+"] couldn't add item to '"+a+"' due to missing property '"+c+"'");var d=this.State()[a].filter(function(a){return a[c]==b[c]});if(d.length>0)return void console.error("["+this.$vmId+"] couldn't add item to '"+a+"' because the key already exists")}var e=this.State()[a];e.push(b);var f={};f[a]=e,this.State(f)},d.prototype.$removeList=function(a,b){var c={};c[a]=this.State()[a].filter(function(a){return!b(a)}),this.State(c)},d.prototype.$updateList=function(b,c){var d=this.$itemKey[b];if(null!=d){if(!c.hasOwnProperty(d))return void console.error("["+this.$vmId+"] couldn't update item to '"+b+"' due to missing property '"+d+"'");var e={};e[b]=this.State()[b].map(function(b){return b[d]==c[d]?a.extend(b,c):b}),this.State(e)}else console.error("["+this.$vmId+"] missing item key for '"+listName+"'; add "+listName+"_itemKey property to the view model.")},dotnetify}); \ No newline at end of file diff --git a/dist/dotnetify-react.router.js b/dist/dotnetify-react.router.js index c4efed95..b6c13578 100644 --- a/dist/dotnetify-react.router.js +++ b/dist/dotnetify-react.router.js @@ -227,7 +227,7 @@ limitations under the License. // Add plugin functions. dotnetify.react.router = { - version: "1.0.6-beta", + version: "1.1.0", // URL path that will be parsed when performing routing. urlPath: document.location.pathname, diff --git a/dist/dotnetify-react.scope.js b/dist/dotnetify-react.scope.js index 965338a3..c322bee7 100644 --- a/dist/dotnetify-react.scope.js +++ b/dist/dotnetify-react.scope.js @@ -35,7 +35,7 @@ limitations under the License. // injects properties and dispatch functions into the component. dotnetify.react.Scope = createReactClass({ displayName: "Scope", - version: "1.0.2", + version: "1.1.0", propTypes: { vm: PropTypes.string }, contextTypes: { scoped: PropTypes.func }, diff --git a/dist/dotnetify.js b/dist/dotnetify.js index 466f4f9e..77e6467f 100644 --- a/dist/dotnetify.js +++ b/dist/dotnetify.js @@ -35,7 +35,7 @@ var dotNetify = {}; dotnetify = $.extend(dotnetify, { - version: "1.1.5", + version: "1.2.0", // SignalR hub options. hub: dotnetifyHub, @@ -65,6 +65,12 @@ var dotNetify = {}; dotnetifyHub.client.response_VM = function (iVMId, iVMData) { + // SignalR .NET Core is sending an array of arguments. + if (Array.isArray(iVMId)) { + iVMData = iVMId[1]; + iVMId = iVMId[0]; + } + // Report unauthorized access. if (iVMData == "403") { console.error("Unauthorized access to " + iVMId); diff --git a/dist/dotnetify.min.js b/dist/dotnetify.min.js index 7831c9e1..91c02c0d 100644 --- a/dist/dotnetify.min.js +++ b/dist/dotnetify.min.js @@ -1 +1 @@ -var dotnetify="undefined"==typeof dotnetify?{}:dotnetify,dotNetify={};!function(a){"function"==typeof define&&define.amd?define(["jquery","knockout","ko-mapping","dotnetify-hub","jquery-ui"],a):"object"==typeof exports&&"object"==typeof module?module.exports=a(require("jquery"),require("knockout"),require("ko-mapping"),require("jquery-ui"),require("./dotnetify-hub")):a(jQuery,ko,ko.mapping,dotnetifyHub)}(function(a,b,c,d){return b.mapping=c,dotnetify=a.extend(dotnetify,{version:"1.1.5",hub:d,hubLib:null,hubOptions:{transport:["webSockets","longPolling"]},hubPath:null,hubServerUrl:null,debug:!1,debugFn:null,offline:!1,isOffline:!0,offlineTimeout:5e3,offlineCacheFn:null,_hub:null,init:function(){var b=function(){a.each(a("[data-vm]"),function(){a(this).dotnetify()})};if(null===dotnetify._hub){d.init(dotnetify.hubPath,dotnetify.hubServerUrl,dotnetify.hubLib),d.client.response_VM=function(b,c){if("403"==c)return void console.error("Unauthorized access to "+b);var e=b,f=null;if(e.indexOf("$")>=0){var g=b.split("$");e=g[0],f=g[1]}var h="[data-vm='"+e+"']",g=e.split(".");if(g.length>1){for(h="",i=0;i0?j.data("ko-dotnetify").UpdateVM(c):d.server.dispose_VM(b)};var c=function(){var c=d.start(dotnetify.hubOptions).done(function(){dotnetify._connectRetry=0,b()}).fail(function(a){console.error(a)});return setTimeout(function(){dotnetify.offline&&!dotnetify.isConnected()&&(b(),dotnetify.isOffline=!0,a(document).trigger("offline",dotnetify.isOffline))},dotnetify.offlineTimeout),c};dotnetify._hub=c(),d.disconnected(function(){setTimeout(function(){dotnetify._hub=c()},5e3*dotnetify._connectRetry+500),dotnetify._connectRetry<3&&dotnetify._connectRetry++}),d.stateChanged(function(b){console.log("SignalR: "+b);var c="connected"!=b;dotnetify.isOffline!=c&&(dotnetify.isOffline=c,a(document).trigger("offline",dotnetify.isOffline))})}else dotnetify.isConnected()?dotnetify._hub.done(b):dotnetify.offline&&b()},isConnected:function(){return d.isConnected},widget:function(b){return a(b).data("ko-dotnetify")},plugins:{},_connectRetry:0}),dotNetify=dotnetify,a(function(){dotnetify.init()}),a.widget("ko.dotnetify",{_create:function(){var b=this;b.VMType=b.element.attr("data-vm"),b.VMId=b.VMType,b.Hub=d;var c=b.element.attr("data-vm-id");null!=c&&(b.VMId+="$"+c),a.each(b.element.parents("[data-master-vm]"),function(){b.VMId=a(this).attr("data-master-vm")+"."+b.VMId}),dotnetify.offline&&b._ListenToOfflineEvent(),null!=b.VMId?dotnetify.isConnected()?b._RequestVM():dotnetify.offline&&b._GetOfflineVM():console.error("ERROR: dotnetify - failed to find 'data-vm' attribute in the element where .dotnetify() was applied.")},_destroy:function(){try{var b=this;"function"==typeof b.OfflineFn&&a(document).off("offline",b.OfflineFn),a.each(dotnetify.plugins,function(a,c){"function"==typeof c.$destroy&&c.$destroy.apply(b.VM)}),null!=b.VM&&b.VM.hasOwnProperty("$destroy")&&b.VM.$destroy()}catch(a){console.error(a.stack)}this.Hub.server.dispose_VM(b.VMId)},UpdateVM:function(c){var d=this;try{if(null==d.VM){d.VM=b.mapping.fromJS(JSON.parse(c)),d.VM.$vmId=d.VMId,d.VM.$element=d.element,dotnetify.offline&&(d.VM.$vmOffline=b.observable(d.IsOffline)),this._AddBuiltInFunctions(),a.each(dotnetify.plugins,function(a,b){"function"==typeof b.$init&&b.$init.apply(d.VM)}),"function"==typeof d.VM.$init&&d.VM.$init();try{b.applyBindings(d.VM,d.element[0])}catch(a){console.error(a.stack)}d.VM.$serverUpdate=!0,d._OnComponentReady(function(){a.each(dotnetify.plugins,function(a,b){"function"==typeof b.$ready&&b.$ready.apply(d.VM)}),d._SubscribeObservables(d.VM),"function"==typeof d.VM.$ready&&d.VM.$ready(),d.element.trigger("ready",{VMId:d.VMId,VM:d.VM})}),dotnetify.offline&&dotnetify.isConnected()&&"function"==typeof dotnetify.offlineCacheFn&&dotnetify.offlineCacheFn(d.VMId+d.element.attr("data-vm-arg"),c)}else{d.VM.$serverUpdate=!1;var e=JSON.parse(c);d._PreProcess(e);try{b.mapping.fromJS(e,d.VM)}catch(a){console.error(a.stack)}d.VM.$serverUpdate=!0,d._OnComponentReady(function(){d._SubscribeObservables(d.VM)})}}catch(a){console.error(a.stack)}dotnetify.debug&&(console.log("["+d.VMId+"] received> "),console.log(JSON.parse(c)),null!=dotnetify.debugFn&&dotnetify.debugFn(d.VMId,"received",JSON.parse(c)))},_AddBuiltInFunctions:function(){var c=this;c.VM.$preventBinding=function(a){c.VM.$serverUpdate=!1,a.apply(c.VM),c.VM.$serverUpdate=!0},c.VM.$addList=function(a,c){var d=b.mapping.fromJS(c),e=a().$vmKey;if(null!=e){var f=b.utils.arrayFirst(a(),function(a){return a[e]()==d[e]()});if(null!=f)return void a.replace(f,d)}a.push(d)},c.VM.$updateList=function(a,c){var d=b.mapping.fromJS(c),e=a().$vmKey;if(null!=e){if(!d.hasOwnProperty(e))return void console.error("ERROR: object requires property '"+e+"'");var f=b.utils.arrayFirst(a(),function(a){return a[e]()==d[e]()});if(null!=f){for(prop in d)b.isObservable(d[prop])&&f[prop](d[prop]());return}}a.push(d)},c.VM.$removeList=function(a,b){c.VM.$preventBinding(function(){a.remove(b)})},c.VM.$on=function(a,b){a.subscribe(function(a){b(a)})},c.VM.$once=function(a,b){var c=a.subscribe(function(a){c.dispose(),b(a)})},c.VM.$loadView=function(b,c,d,e,f){return"object"==typeof d&&null!=d?(f=e,e=d,d=null):"function"==typeof d?(f=d,d=null):"function"==typeof e&&(f=e,e=null),null==c||""==c?void a(b).empty():void a(b).load(c,null,function(){null==e||a.isEmptyObject(e)||a(this).attr("data-vm-arg",JSON.stringify(e)),"function"==typeof f&&f.apply(this),null!=d?a.getScript(d,function(){dotnetify.init()}):dotnetify.init()})},c.VM.$inject=function(d,e){b.isObservable(d)&&"push"in d?a.each(d(),function(a,b){c._Inject(b,e)}):c._Inject(d,e)};var d=window[c.VMType];null!=d&&("function"==typeof d&&(d=new d),c._Inject(c.VM,d)),a.each(dotnetify.plugins,function(a,b){b.hasOwnProperty("$inject")&&b.$inject(c.VM)})},_GetOfflineVM:function(){var a=this;if("function"==typeof dotnetify.offlineCacheFn){var b=dotnetify.offlineCacheFn(a.VMId+a.element.attr("data-vm-arg"));null==b&&(b=dotnetify.offlineCacheFn(a.VMId)),null!=b&&(dotnetify.debug&&console.warn("["+a.VMId+"] using offline data"),a.IsOffline=!0,a.UpdateVM(b))}},_ListenToOfflineEvent:function(){var b=this;b.IsOffline=!1,b.OfflineFn=function(c,d){null!=b.VM&&b.VM.hasOwnProperty("$vmOffline")&&b.VM.$vmOffline(d),b.IsOffline=d,d?null==b.VM&&b._GetOfflineVM():b._RequestVM(),a(document).one("offline",b.OfflineFn.bind(b))},a(document).one("offline",b.OfflineFn.bind(b))},_Inject:function(a,c){for(prop in c)a.hasOwnProperty(prop)||("function"==typeof c[prop]?0==prop.indexOf("_")?a[prop]=b.pureComputed(c[prop],a):a[prop]=c[prop]:0==prop.indexOf("_")&&(a[prop]=b.observable(c[prop]),a[prop].$subscribe=!0))},_OnComponentReady:function(b){var c=this,d=0,e=function(){var f=!0,g=c.element.find("[params]");g.length>0&&(f=0==a.grep(g,function(a){return 0==a.childElementCount}).length),f||d++>3?b():setTimeout(e,250)};e()},_OnValueChanged:function(b,c){var d={};if(d[b]=c instanceof Object?a.extend({},c):c,dotnetify.isConnected())try{this.Hub.server.update_VM(this.VMId,d),dotnetify.debug&&(console.log("["+this.VMId+"] sent> "),console.log(d),null!=dotnetify.debugFn&&dotnetify.debugFn(this.VMId,"sent",d))}catch(a){console.error(a)}},_PreProcess:function(a){for(var b in a){var c=/(.*)_add/.exec(b);if(null==c){var c=/(.*)_update/.exec(b);if(null==c){var c=/(.*)_remove/.exec(b);if(null==c);else{var d=this.VM[c[1]];if(null==d)throw new Error("unable to resolve "+b);var e=d().$vmKey;if(null==e)throw new Error("unable to resolve "+b+" due to missing vmItemKey attribute");this.VM.$removeList(this.VM[c[1]],function(c){return c[e]()==a[b]}),delete a[b]}}else{var d=this.VM[c[1]];if(null==d)throw new Error("unable to resolve "+b);this.VM.$updateList(d,a[b]),delete a[b]}}else{var d=this.VM[c[1]];if(null==d)throw new Error("unable to resolve "+b);this.VM.$addList(d,a[b]),delete a[b]}}},_RequestVM:function(){var b=this,c=b.element.attr("data-vm-arg");if(c=null!=c?a.parseJSON(c.replace(/'/g,'"')):null,dotnetify.isConnected())try{b.Hub.server.request_VM(b.VMId,c)}catch(a){console.error(a)}},_SubscribeObservables:function(a,c){var d=this;if(null!=a)if(b.isObservable(a))"$subscribe"in a==!1&&(a.subscribe(function(a){d.VM.$serverUpdate===!0&&d._OnValueChanged(c,a)}),a.$subscribe=!0),this._SubscribeObservables(a(),c);else if("object"==typeof a){var e="$vmKey"in a?a.$vmKey:null;for(property in a)"$"!=property.charAt(0)&&"_"!=property.charAt(0)&&(path=null!=e?"$"+a[property][e]():property,this._SubscribeObservables(a[property],null==c?path:c+"."+path))}else if(a instanceof Array)for(index in a)path="$"+index,this._SubscribeObservables(a[index],null==c?path:c+"."+path)}}),b.bindingHandlers.vmItemKey={preprocess:function(a){return"'"!=a.charAt(0)?"'"+a+"'":a},update:function(a,c,d,e,f){var g=c(),h=d.get("foreach");!b.isObservable(h)&&h.hasOwnProperty("data")&&(h=h.data),b.isObservable(h)&&null!=h()&&null!=g&&(h().$vmKey=g)}},b.bindingHandlers.vmCommand={init:function(a,c,d,e,f){var g=f.$root,h=null,i=null,j=/return\s{(.*):(.*)}\s/.exec(c.toString());if(null!=j)h=j[1].trim(),i=j[2].trim();else{var k=/return\s(.*)\s/.exec(c.toString());null!=k&&(h=k[1].trim())}if(null==h)throw new Error("invalid vmCommand value at "+a.outerHTML);var l=function(){return null!=g[h]?g[h]:c()};null!=i?"'"==i.charAt(0)?i=i.replace(/(^'|'$)/g,""):b.isObservable(e[i])?i=b.unwrap(e[i]):"$data"==i&&(i=e):i=!0;var m=function(){return function(){var c=l();b.isObservable(c)?(g.$preventBinding(function(){c(i!==!0&&null)}),c(i)):c.apply(g,[e,a,f.$parent])}};b.bindingHandlers.click.init(a,m,d,e,f)}},b.bindingHandlers.vmOnce={init:function(a,c,d,e,f){b.bindingHandlers.vmOn.init(a,c,d,e,f,!0)}},b.bindingHandlers.vmOn={init:function(a,c,d,e,f,g){var h=f.$root,i=null,j=null,k=c.toString(),l=/return\s{(.*):(.*)}\s/.exec(k);if(null!=l&&(i=l[1].trim(),j=l[2].trim()),null==j)throw new Error("invalid vmOn function at "+a.outerHTML);var m=function(){return null!=h[j]?h[j]:c()[i]};if(null!=i&&!b.isObservable(e[i]))throw new Error("invalid vmOn data: "+c());m().apply(h,[e,a,f.$parent]),null==g&&e[i].subscribe(function(b){m().apply(h,[e,a,f.$parent])})}},dotnetify}),function(a){"function"==typeof define&&define.amd?define(["jquery","knockout","dotnetify"],a):"object"==typeof exports&&"object"==typeof module?module.exports=a(require("jquery"),require("knockout"),require("dotnetify")):a(jQuery,ko,dotnetify)}(function(a,b,c){var d={version:"0.8.4",map:function(a){return d.routes.defined.hasOwnProperty(a)?d.routes.defined[a]:new d.core.route(a)},root:function(a){d.routes.root=a},rescue:function(a){d.routes.rescue=a},history:{initial:{},pushState:function(a,b,c){d.history.supported?d.dispatch(c)&&history.pushState(a,b,c):d.history.fallback&&(window.location.hash="#"+c)},popState:function(a){var b=!d.history.initial.popped&&location.href==d.history.initial.URL;d.history.initial.popped=!0,b||d.dispatch(document.location.pathname)},listen:function(a){if(d.history.supported=!(!window.history||!window.history.pushState),d.history.fallback=a,d.history.supported)d.history.initial.popped="state"in window.history,d.history.initial.URL=location.href,window.onpopstate=d.history.popState;else if(d.history.fallback){for(route in d.routes.defined)"#"!=route.charAt(0)&&(d.routes.defined["#"+route]=d.routes.defined[route],d.routes.defined["#"+route].path="#"+route);d.listen()}}},match:function(a,b){var c,e,f,g,h,i={},j=null;for(j in d.routes.defined)if(null!==j&&void 0!==j)for(j=d.routes.defined[j],c=j.partition(),g=0;g0)for(f=0;f=8)?window.onhashchange=a:setInterval(a,50),""!==location.hash&&d.dispatch(location.hash)},core:{route:function(a){this.path=a,this.action=null,this.do_enter=[],this.do_exit=null,this.params={},d.routes.defined[a]=this}},routes:{current:null,root:null,rescue:null,previous:null,defined:{}}};d.core.route.prototype={to:function(a){return this.action=a,this},enter:function(a){return a instanceof Array?this.do_enter=this.do_enter.concat(a):this.do_enter.push(a),this},exit:function(a){return this.do_exit=a,this},partition:function(){for(var a,b,c=[],d=[],e=/\(([^}]+?)\)/g;a=e.exec(this.path);)c.push(a[1]);for(d.push(this.path.split("(")[0]),b=0;b0)for(a=0;a=0;)a=a.substr(0,a.length-1);for(;0==a.indexOf("/");)a=a.substr(1,a.length-1);return a},equal:function(a,b){return null!=a&&null!=b&&a.toLowerCase()==b.toLowerCase()},startsWith:function(a,b){return a.toLowerCase().slice(0,b.length)==b.toLowerCase()},endsWith:function(a,b){return""==b||a.toLowerCase().slice(-b.length)==b.toLowerCase()}}}();return{initRoot:function(){var a=this;if(a.hasOwnProperty("RoutingState")&&"function"==typeof a.RoutingState.Root&&a.$router._absRoot!=a.RoutingState.Root()){var b=d.trim(a.$element.attr("data-vm-root"));""!=b&&(b="/"+b);var c=d.trim(a.RoutingState.Root());a.$router._absRoot=""!=c?b+"/"+c:b,a.RoutingState.Root(a.$router._absRoot)}}.bind(b),initRouting:function(){var b=this;if(b.hasOwnProperty("RoutingState")&&"function"==typeof b.RoutingState.Templates){var d=b.RoutingState.Templates();if(null!=d){c.router.$init||(c.router.init(),c.router.$init=!0),b.$router.initRoot(),a.each(d,function(a,d){var e=b.$router.toUrl(d.UrlPattern());c.debug&&console.log("router> map "+e+" to template id="+d.Id()),c.router.mapTo(e,function(a){c.router.urlPath="";var e=d.UrlPattern();for(param in a)e=e.replace(":"+param,a[param]);e=e.replace(/\(\/:([^)]+)\)/g,"").replace(/\(|\)/g,""),b.$router.routeTo(e,d)})});var e=b.$router.toUrl(b.RoutingState.Active());""==c.router.urlPath&&(c.router.urlPath=e),b.$router.routeUrl()}}}.bind(b),isActive:function(a){return!(null==a||!a.hasOwnProperty("Path")||"function"!=typeof a.Path)&&d.equal(a.Path(),this.RoutingState.Active())}.bind(b),loadView:function(b,e,f,g,h){var i=this;if(null==e||""==e)return void a(b).empty();var j=function(){i.hasOwnProperty("RoutingState")&&"function"==typeof i.RoutingState.Root&&a.each(a(this).find("[data-vm]"),function(b,c){var e=a(c).attr("data-vm-root");if(e=null!=e?"/"+d.trim(i.RoutingState.Root())+"/"+d.trim(e):i.RoutingState.Root(),a(c).attr("data-vm-root",e),null!=g&&!a.isEmptyObject(g)){var f=a(c).attr("data-vm-arg");f=null!=f?a.extend(g,a.parseJSON(f.replace(/'/g,'"'))):g,a(c).attr("data-vm-arg",JSON.stringify(f))}}),"function"==typeof h&&h.apply(this)};e=c.router.overrideUrl(e),i.$loadView(b,e,f,g,j)}.bind(b),manualRouteTo:function(a,b,c,d){var e={};e.Target=function(){return b},e.ViewUrl=function(){return c},e.JSModuleUrl=function(){return d},this.$router.routeTo(a,e,!0)}.bind(b),routeTo:function(b,e,f){var g=this;c.debug&&console.log("router> route '"+b+"' to template id="+e.Id());var h=a("#"+e.Target()+" [data-vm-arg]").attr("data-vm-arg");if(!(null!=h&&(h=a.parseJSON(h.replace(/'/g,'"')),"string"==typeof h["RoutingState.Origin"]&&d.equal(h["RoutingState.Origin"],b))||1!=f&&g.hasOwnProperty("onRouteEnter")&&0==g.onRouteEnter(b,e)))return 0==a("#"+e.Target()).length?c.router.redirect(g.$router.toUrl(b)):void g.$router.loadView("#"+e.Target(),e.ViewUrl(),e.JSModuleUrl(),{"RoutingState.Origin":b},function(){g.RoutingState.Active(b),1!=f&&g.hasOwnProperty("onRouteExit")&&g.onRouteExit(b,e)}.bind(g))}.bind(b),routeUrl:function(){var b=this;if(!b.hasOwnProperty("RoutingState")||"function"!=typeof b.RoutingState.Templates)return!1;var e=b.RoutingState.Root();if(null==e)return!1;var f=c.router.urlPath;if(c.debug&&console.log("router> routing "+f),d.equal(f,e)){var g=a.grep(b.RoutingState.Templates(),function(a){return""===a.UrlPattern()});return void(g.length>0&&b.$router.routeTo("",g[0]))}if(e+="/",d.startsWith(f,e)){var h=null,g=a.grep(b.$element.find("[data-vm-route]"),function(b){return d.startsWith(f,a(b).attr("href"))});if(g.length>0)for(i=0;i0)l=m[0],null==path&&(path=l.UrlPattern(),k.Path(path));else if(null==k.RedirectRoot())throw new Error("vmRoute cannot find route template '"+k.TemplateId()+"' at "+d.outerHTML)}if(null!=k.RedirectRoot()){var n=k.RedirectRoot();"/"==n.charAt(0)&&(n=n.substr(0,n.length-1));var o=k.RedirectRoot().split("/"),p="",q=j.$element.attr("data-vm-root");if(null!=q){var r=q.split("/");for(i=0;i=0){var g=b.split("$");e=g[0],f=g[1]}var h="[data-vm='"+e+"']",g=e.split(".");if(g.length>1){for(h="",i=0;i0?j.data("ko-dotnetify").UpdateVM(c):d.server.dispose_VM(b)};var c=function(){var c=d.start(dotnetify.hubOptions).done(function(){dotnetify._connectRetry=0,b()}).fail(function(a){console.error(a)});return setTimeout(function(){dotnetify.offline&&!dotnetify.isConnected()&&(b(),dotnetify.isOffline=!0,a(document).trigger("offline",dotnetify.isOffline))},dotnetify.offlineTimeout),c};dotnetify._hub=c(),d.disconnected(function(){setTimeout(function(){dotnetify._hub=c()},5e3*dotnetify._connectRetry+500),dotnetify._connectRetry<3&&dotnetify._connectRetry++}),d.stateChanged(function(b){console.log("SignalR: "+b);var c="connected"!=b;dotnetify.isOffline!=c&&(dotnetify.isOffline=c,a(document).trigger("offline",dotnetify.isOffline))})}else dotnetify.isConnected()?dotnetify._hub.done(b):dotnetify.offline&&b()},isConnected:function(){return d.isConnected},widget:function(b){return a(b).data("ko-dotnetify")},plugins:{},_connectRetry:0}),dotNetify=dotnetify,a(function(){dotnetify.init()}),a.widget("ko.dotnetify",{_create:function(){var b=this;b.VMType=b.element.attr("data-vm"),b.VMId=b.VMType,b.Hub=d;var c=b.element.attr("data-vm-id");null!=c&&(b.VMId+="$"+c),a.each(b.element.parents("[data-master-vm]"),function(){b.VMId=a(this).attr("data-master-vm")+"."+b.VMId}),dotnetify.offline&&b._ListenToOfflineEvent(),null!=b.VMId?dotnetify.isConnected()?b._RequestVM():dotnetify.offline&&b._GetOfflineVM():console.error("ERROR: dotnetify - failed to find 'data-vm' attribute in the element where .dotnetify() was applied.")},_destroy:function(){try{var b=this;"function"==typeof b.OfflineFn&&a(document).off("offline",b.OfflineFn),a.each(dotnetify.plugins,function(a,c){"function"==typeof c.$destroy&&c.$destroy.apply(b.VM)}),null!=b.VM&&b.VM.hasOwnProperty("$destroy")&&b.VM.$destroy()}catch(a){console.error(a.stack)}this.Hub.server.dispose_VM(b.VMId)},UpdateVM:function(c){var d=this;try{if(null==d.VM){d.VM=b.mapping.fromJS(JSON.parse(c)),d.VM.$vmId=d.VMId,d.VM.$element=d.element,dotnetify.offline&&(d.VM.$vmOffline=b.observable(d.IsOffline)),this._AddBuiltInFunctions(),a.each(dotnetify.plugins,function(a,b){"function"==typeof b.$init&&b.$init.apply(d.VM)}),"function"==typeof d.VM.$init&&d.VM.$init();try{b.applyBindings(d.VM,d.element[0])}catch(a){console.error(a.stack)}d.VM.$serverUpdate=!0,d._OnComponentReady(function(){a.each(dotnetify.plugins,function(a,b){"function"==typeof b.$ready&&b.$ready.apply(d.VM)}),d._SubscribeObservables(d.VM),"function"==typeof d.VM.$ready&&d.VM.$ready(),d.element.trigger("ready",{VMId:d.VMId,VM:d.VM})}),dotnetify.offline&&dotnetify.isConnected()&&"function"==typeof dotnetify.offlineCacheFn&&dotnetify.offlineCacheFn(d.VMId+d.element.attr("data-vm-arg"),c)}else{d.VM.$serverUpdate=!1;var e=JSON.parse(c);d._PreProcess(e);try{b.mapping.fromJS(e,d.VM)}catch(a){console.error(a.stack)}d.VM.$serverUpdate=!0,d._OnComponentReady(function(){d._SubscribeObservables(d.VM)})}}catch(a){console.error(a.stack)}dotnetify.debug&&(console.log("["+d.VMId+"] received> "),console.log(JSON.parse(c)),null!=dotnetify.debugFn&&dotnetify.debugFn(d.VMId,"received",JSON.parse(c)))},_AddBuiltInFunctions:function(){var c=this;c.VM.$preventBinding=function(a){c.VM.$serverUpdate=!1,a.apply(c.VM),c.VM.$serverUpdate=!0},c.VM.$addList=function(a,c){var d=b.mapping.fromJS(c),e=a().$vmKey;if(null!=e){var f=b.utils.arrayFirst(a(),function(a){return a[e]()==d[e]()});if(null!=f)return void a.replace(f,d)}a.push(d)},c.VM.$updateList=function(a,c){var d=b.mapping.fromJS(c),e=a().$vmKey;if(null!=e){if(!d.hasOwnProperty(e))return void console.error("ERROR: object requires property '"+e+"'");var f=b.utils.arrayFirst(a(),function(a){return a[e]()==d[e]()});if(null!=f){for(prop in d)b.isObservable(d[prop])&&f[prop](d[prop]());return}}a.push(d)},c.VM.$removeList=function(a,b){c.VM.$preventBinding(function(){a.remove(b)})},c.VM.$on=function(a,b){a.subscribe(function(a){b(a)})},c.VM.$once=function(a,b){var c=a.subscribe(function(a){c.dispose(),b(a)})},c.VM.$loadView=function(b,c,d,e,f){return"object"==typeof d&&null!=d?(f=e,e=d,d=null):"function"==typeof d?(f=d,d=null):"function"==typeof e&&(f=e,e=null),null==c||""==c?void a(b).empty():void a(b).load(c,null,function(){null==e||a.isEmptyObject(e)||a(this).attr("data-vm-arg",JSON.stringify(e)),"function"==typeof f&&f.apply(this),null!=d?a.getScript(d,function(){dotnetify.init()}):dotnetify.init()})},c.VM.$inject=function(d,e){b.isObservable(d)&&"push"in d?a.each(d(),function(a,b){c._Inject(b,e)}):c._Inject(d,e)};var d=window[c.VMType];null!=d&&("function"==typeof d&&(d=new d),c._Inject(c.VM,d)),a.each(dotnetify.plugins,function(a,b){b.hasOwnProperty("$inject")&&b.$inject(c.VM)})},_GetOfflineVM:function(){var a=this;if("function"==typeof dotnetify.offlineCacheFn){var b=dotnetify.offlineCacheFn(a.VMId+a.element.attr("data-vm-arg"));null==b&&(b=dotnetify.offlineCacheFn(a.VMId)),null!=b&&(dotnetify.debug&&console.warn("["+a.VMId+"] using offline data"),a.IsOffline=!0,a.UpdateVM(b))}},_ListenToOfflineEvent:function(){var b=this;b.IsOffline=!1,b.OfflineFn=function(c,d){null!=b.VM&&b.VM.hasOwnProperty("$vmOffline")&&b.VM.$vmOffline(d),b.IsOffline=d,d?null==b.VM&&b._GetOfflineVM():b._RequestVM(),a(document).one("offline",b.OfflineFn.bind(b))},a(document).one("offline",b.OfflineFn.bind(b))},_Inject:function(a,c){for(prop in c)a.hasOwnProperty(prop)||("function"==typeof c[prop]?0==prop.indexOf("_")?a[prop]=b.pureComputed(c[prop],a):a[prop]=c[prop]:0==prop.indexOf("_")&&(a[prop]=b.observable(c[prop]),a[prop].$subscribe=!0))},_OnComponentReady:function(b){var c=this,d=0,e=function(){var f=!0,g=c.element.find("[params]");g.length>0&&(f=0==a.grep(g,function(a){return 0==a.childElementCount}).length),f||d++>3?b():setTimeout(e,250)};e()},_OnValueChanged:function(b,c){var d={};if(d[b]=c instanceof Object?a.extend({},c):c,dotnetify.isConnected())try{this.Hub.server.update_VM(this.VMId,d),dotnetify.debug&&(console.log("["+this.VMId+"] sent> "),console.log(d),null!=dotnetify.debugFn&&dotnetify.debugFn(this.VMId,"sent",d))}catch(a){console.error(a)}},_PreProcess:function(a){for(var b in a){var c=/(.*)_add/.exec(b);if(null==c){var c=/(.*)_update/.exec(b);if(null==c){var c=/(.*)_remove/.exec(b);if(null==c);else{var d=this.VM[c[1]];if(null==d)throw new Error("unable to resolve "+b);var e=d().$vmKey;if(null==e)throw new Error("unable to resolve "+b+" due to missing vmItemKey attribute");this.VM.$removeList(this.VM[c[1]],function(c){return c[e]()==a[b]}),delete a[b]}}else{var d=this.VM[c[1]];if(null==d)throw new Error("unable to resolve "+b);this.VM.$updateList(d,a[b]),delete a[b]}}else{var d=this.VM[c[1]];if(null==d)throw new Error("unable to resolve "+b);this.VM.$addList(d,a[b]),delete a[b]}}},_RequestVM:function(){var b=this,c=b.element.attr("data-vm-arg");if(c=null!=c?a.parseJSON(c.replace(/'/g,'"')):null,dotnetify.isConnected())try{b.Hub.server.request_VM(b.VMId,c)}catch(a){console.error(a)}},_SubscribeObservables:function(a,c){var d=this;if(null!=a)if(b.isObservable(a))"$subscribe"in a==!1&&(a.subscribe(function(a){d.VM.$serverUpdate===!0&&d._OnValueChanged(c,a)}),a.$subscribe=!0),this._SubscribeObservables(a(),c);else if("object"==typeof a){var e="$vmKey"in a?a.$vmKey:null;for(property in a)"$"!=property.charAt(0)&&"_"!=property.charAt(0)&&(path=null!=e?"$"+a[property][e]():property,this._SubscribeObservables(a[property],null==c?path:c+"."+path))}else if(a instanceof Array)for(index in a)path="$"+index,this._SubscribeObservables(a[index],null==c?path:c+"."+path)}}),b.bindingHandlers.vmItemKey={preprocess:function(a){return"'"!=a.charAt(0)?"'"+a+"'":a},update:function(a,c,d,e,f){var g=c(),h=d.get("foreach");!b.isObservable(h)&&h.hasOwnProperty("data")&&(h=h.data),b.isObservable(h)&&null!=h()&&null!=g&&(h().$vmKey=g)}},b.bindingHandlers.vmCommand={init:function(a,c,d,e,f){var g=f.$root,h=null,i=null,j=/return\s{(.*):(.*)}\s/.exec(c.toString());if(null!=j)h=j[1].trim(),i=j[2].trim();else{var k=/return\s(.*)\s/.exec(c.toString());null!=k&&(h=k[1].trim())}if(null==h)throw new Error("invalid vmCommand value at "+a.outerHTML);var l=function(){return null!=g[h]?g[h]:c()};null!=i?"'"==i.charAt(0)?i=i.replace(/(^'|'$)/g,""):b.isObservable(e[i])?i=b.unwrap(e[i]):"$data"==i&&(i=e):i=!0;var m=function(){return function(){var c=l();b.isObservable(c)?(g.$preventBinding(function(){c(i!==!0&&null)}),c(i)):c.apply(g,[e,a,f.$parent])}};b.bindingHandlers.click.init(a,m,d,e,f)}},b.bindingHandlers.vmOnce={init:function(a,c,d,e,f){b.bindingHandlers.vmOn.init(a,c,d,e,f,!0)}},b.bindingHandlers.vmOn={init:function(a,c,d,e,f,g){var h=f.$root,i=null,j=null,k=c.toString(),l=/return\s{(.*):(.*)}\s/.exec(k);if(null!=l&&(i=l[1].trim(),j=l[2].trim()),null==j)throw new Error("invalid vmOn function at "+a.outerHTML);var m=function(){return null!=h[j]?h[j]:c()[i]};if(null!=i&&!b.isObservable(e[i]))throw new Error("invalid vmOn data: "+c());m().apply(h,[e,a,f.$parent]),null==g&&e[i].subscribe(function(b){m().apply(h,[e,a,f.$parent])})}},dotnetify}),function(a){"function"==typeof define&&define.amd?define(["jquery","knockout","dotnetify"],a):"object"==typeof exports&&"object"==typeof module?module.exports=a(require("jquery"),require("knockout"),require("dotnetify")):a(jQuery,ko,dotnetify)}(function(a,b,c){var d={version:"0.8.4",map:function(a){return d.routes.defined.hasOwnProperty(a)?d.routes.defined[a]:new d.core.route(a)},root:function(a){d.routes.root=a},rescue:function(a){d.routes.rescue=a},history:{initial:{},pushState:function(a,b,c){d.history.supported?d.dispatch(c)&&history.pushState(a,b,c):d.history.fallback&&(window.location.hash="#"+c)},popState:function(a){var b=!d.history.initial.popped&&location.href==d.history.initial.URL;d.history.initial.popped=!0,b||d.dispatch(document.location.pathname)},listen:function(a){if(d.history.supported=!(!window.history||!window.history.pushState),d.history.fallback=a,d.history.supported)d.history.initial.popped="state"in window.history,d.history.initial.URL=location.href,window.onpopstate=d.history.popState;else if(d.history.fallback){for(route in d.routes.defined)"#"!=route.charAt(0)&&(d.routes.defined["#"+route]=d.routes.defined[route],d.routes.defined["#"+route].path="#"+route);d.listen()}}},match:function(a,b){var c,e,f,g,h,i={},j=null;for(j in d.routes.defined)if(null!==j&&void 0!==j)for(j=d.routes.defined[j],c=j.partition(),g=0;g0)for(f=0;f=8)?window.onhashchange=a:setInterval(a,50),""!==location.hash&&d.dispatch(location.hash)},core:{route:function(a){this.path=a,this.action=null,this.do_enter=[],this.do_exit=null,this.params={},d.routes.defined[a]=this}},routes:{current:null,root:null,rescue:null,previous:null,defined:{}}};d.core.route.prototype={to:function(a){return this.action=a,this},enter:function(a){return a instanceof Array?this.do_enter=this.do_enter.concat(a):this.do_enter.push(a),this},exit:function(a){return this.do_exit=a,this},partition:function(){for(var a,b,c=[],d=[],e=/\(([^}]+?)\)/g;a=e.exec(this.path);)c.push(a[1]);for(d.push(this.path.split("(")[0]),b=0;b0)for(a=0;a=0;)a=a.substr(0,a.length-1);for(;0==a.indexOf("/");)a=a.substr(1,a.length-1);return a},equal:function(a,b){return null!=a&&null!=b&&a.toLowerCase()==b.toLowerCase()},startsWith:function(a,b){return a.toLowerCase().slice(0,b.length)==b.toLowerCase()},endsWith:function(a,b){return""==b||a.toLowerCase().slice(-b.length)==b.toLowerCase()}}}();return{initRoot:function(){var a=this;if(a.hasOwnProperty("RoutingState")&&"function"==typeof a.RoutingState.Root&&a.$router._absRoot!=a.RoutingState.Root()){var b=d.trim(a.$element.attr("data-vm-root"));""!=b&&(b="/"+b);var c=d.trim(a.RoutingState.Root());a.$router._absRoot=""!=c?b+"/"+c:b,a.RoutingState.Root(a.$router._absRoot)}}.bind(b),initRouting:function(){var b=this;if(b.hasOwnProperty("RoutingState")&&"function"==typeof b.RoutingState.Templates){var d=b.RoutingState.Templates();if(null!=d){c.router.$init||(c.router.init(),c.router.$init=!0),b.$router.initRoot(),a.each(d,function(a,d){var e=b.$router.toUrl(d.UrlPattern());c.debug&&console.log("router> map "+e+" to template id="+d.Id()),c.router.mapTo(e,function(a){c.router.urlPath="";var e=d.UrlPattern();for(param in a)e=e.replace(":"+param,a[param]);e=e.replace(/\(\/:([^)]+)\)/g,"").replace(/\(|\)/g,""),b.$router.routeTo(e,d)})});var e=b.$router.toUrl(b.RoutingState.Active());""==c.router.urlPath&&(c.router.urlPath=e),b.$router.routeUrl()}}}.bind(b),isActive:function(a){return!(null==a||!a.hasOwnProperty("Path")||"function"!=typeof a.Path)&&d.equal(a.Path(),this.RoutingState.Active())}.bind(b),loadView:function(b,e,f,g,h){var i=this;if(null==e||""==e)return void a(b).empty();var j=function(){i.hasOwnProperty("RoutingState")&&"function"==typeof i.RoutingState.Root&&a.each(a(this).find("[data-vm]"),function(b,c){var e=a(c).attr("data-vm-root");if(e=null!=e?"/"+d.trim(i.RoutingState.Root())+"/"+d.trim(e):i.RoutingState.Root(),a(c).attr("data-vm-root",e),null!=g&&!a.isEmptyObject(g)){var f=a(c).attr("data-vm-arg");f=null!=f?a.extend(g,a.parseJSON(f.replace(/'/g,'"'))):g,a(c).attr("data-vm-arg",JSON.stringify(f))}}),"function"==typeof h&&h.apply(this)};e=c.router.overrideUrl(e),i.$loadView(b,e,f,g,j)}.bind(b),manualRouteTo:function(a,b,c,d){var e={};e.Target=function(){return b},e.ViewUrl=function(){return c},e.JSModuleUrl=function(){return d},this.$router.routeTo(a,e,!0)}.bind(b),routeTo:function(b,e,f){var g=this;c.debug&&console.log("router> route '"+b+"' to template id="+e.Id());var h=a("#"+e.Target()+" [data-vm-arg]").attr("data-vm-arg");if(!(null!=h&&(h=a.parseJSON(h.replace(/'/g,'"')),"string"==typeof h["RoutingState.Origin"]&&d.equal(h["RoutingState.Origin"],b))||1!=f&&g.hasOwnProperty("onRouteEnter")&&0==g.onRouteEnter(b,e)))return 0==a("#"+e.Target()).length?c.router.redirect(g.$router.toUrl(b)):void g.$router.loadView("#"+e.Target(),e.ViewUrl(),e.JSModuleUrl(),{"RoutingState.Origin":b},function(){g.RoutingState.Active(b),1!=f&&g.hasOwnProperty("onRouteExit")&&g.onRouteExit(b,e)}.bind(g))}.bind(b),routeUrl:function(){var b=this;if(!b.hasOwnProperty("RoutingState")||"function"!=typeof b.RoutingState.Templates)return!1;var e=b.RoutingState.Root();if(null==e)return!1;var f=c.router.urlPath;if(c.debug&&console.log("router> routing "+f),d.equal(f,e)){var g=a.grep(b.RoutingState.Templates(),function(a){return""===a.UrlPattern()});return void(g.length>0&&b.$router.routeTo("",g[0]))}if(e+="/",d.startsWith(f,e)){var h=null,g=a.grep(b.$element.find("[data-vm-route]"),function(b){return d.startsWith(f,a(b).attr("href"))});if(g.length>0)for(i=0;i0)l=m[0],null==path&&(path=l.UrlPattern(),k.Path(path));else if(null==k.RedirectRoot())throw new Error("vmRoute cannot find route template '"+k.TemplateId()+"' at "+d.outerHTML)}if(null!=k.RedirectRoot()){var n=k.RedirectRoot();"/"==n.charAt(0)&&(n=n.substr(0,n.length-1));var o=k.RedirectRoot().split("/"),p="",q=j.$element.attr("data-vm-root");if(null!=q){var r=q.split("/");for(i=0;i= this.minimumLogLevel) { + switch (logLevel) { + case ILogger.LogLevel.Error: + console.error(ILogger.LogLevel[logLevel] + ": " + message); + break; + case ILogger.LogLevel.Warning: + console.warn(ILogger.LogLevel[logLevel] + ": " + message); + break; + case ILogger.LogLevel.Information: + console.info(ILogger.LogLevel[logLevel] + ": " + message); + break; + default: + console.log(ILogger.LogLevel[logLevel] + ": " + message); + break; + } + } + }; + return ConsoleLogger; + }()); + exports.ConsoleLogger = ConsoleLogger; + var LoggerFactory = /** @class */ (function () { + function LoggerFactory() { + } + LoggerFactory.createLogger = function (logging) { + if (logging === undefined) { + return new ConsoleLogger(ILogger.LogLevel.Information); + } + if (logging === null) { + return new NullLogger(); + } + if (logging.log) { + return logging; + } + return new ConsoleLogger(logging); + }; + return LoggerFactory; + }()); + exports.LoggerFactory = LoggerFactory; }); - unwrapExports(ILogger); - var ILogger_1 = ILogger.LogLevel; + unwrapExports(Loggers); + var Loggers_1 = Loggers.NullLogger; + var Loggers_2 = Loggers.ConsoleLogger; + var Loggers_3 = Loggers.LoggerFactory; var AbortController_1 = createCommonjsModule(function (module, exports) { Object.defineProperty(exports, "__esModule", { value: true }); @@ -1406,6 +1489,31 @@ unwrapExports(AbortController_1); var AbortController_2 = AbortController_1.AbortController; + var Utils = createCommonjsModule(function (module, exports) { + Object.defineProperty(exports, "__esModule", { value: true }); + var Arg = /** @class */ (function () { + function Arg() { + } + Arg.isRequired = function (val, name) { + if (val === null || val === undefined) { + throw new Error("The '" + name + "' argument is required."); + } + }; + Arg.isIn = function (val, values, name) { + // TypeScript enums have keys for **both** the name and the value of each enum member on the type itself. + if (!(val in values)) { + throw new Error("Unknown " + name + " value: " + val + "."); + } + }; + return Arg; + }()); + exports.Arg = Arg; + + }); + + unwrapExports(Utils); + var Utils_1 = Utils.Arg; + var Transports = createCommonjsModule(function (module, exports) { var __awaiter = (commonjsGlobal && commonjsGlobal.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { @@ -1446,19 +1554,33 @@ + var TransportType; (function (TransportType) { TransportType[TransportType["WebSockets"] = 0] = "WebSockets"; TransportType[TransportType["ServerSentEvents"] = 1] = "ServerSentEvents"; TransportType[TransportType["LongPolling"] = 2] = "LongPolling"; })(TransportType = exports.TransportType || (exports.TransportType = {})); + var TransferFormat; + (function (TransferFormat) { + TransferFormat[TransferFormat["Text"] = 1] = "Text"; + TransferFormat[TransferFormat["Binary"] = 2] = "Binary"; + })(TransferFormat = exports.TransferFormat || (exports.TransferFormat = {})); var WebSocketTransport = /** @class */ (function () { function WebSocketTransport(accessTokenFactory, logger) { this.logger = logger; this.accessTokenFactory = accessTokenFactory || (function () { return null; }); } - WebSocketTransport.prototype.connect = function (url, requestedTransferMode, connection) { + WebSocketTransport.prototype.connect = function (url, transferFormat, connection) { var _this = this; + Utils.Arg.isRequired(url, "url"); + Utils.Arg.isRequired(transferFormat, "transferFormat"); + Utils.Arg.isIn(transferFormat, TransferFormat, "transferFormat"); + Utils.Arg.isRequired(connection, "connection"); + if (typeof (WebSocket) === "undefined") { + throw new Error("'WebSocket' is not supported in your environment."); + } + this.logger.log(ILogger.LogLevel.Trace, "(WebSockets transport) Connecting"); return new Promise(function (resolve, reject) { url = url.replace(/^http/, "ws"); var token = _this.accessTokenFactory(); @@ -1466,19 +1588,19 @@ url += (url.indexOf("?") < 0 ? "?" : "&") + ("access_token=" + encodeURIComponent(token)); } var webSocket = new WebSocket(url); - if (requestedTransferMode == 2 /* Binary */) { + if (transferFormat === TransferFormat.Binary) { webSocket.binaryType = "arraybuffer"; } webSocket.onopen = function (event) { _this.logger.log(ILogger.LogLevel.Information, "WebSocket connected to " + url); _this.webSocket = webSocket; - resolve(requestedTransferMode); + resolve(); }; webSocket.onerror = function (event) { - reject(); + reject(event.error); }; webSocket.onmessage = function (message) { - _this.logger.log(ILogger.LogLevel.Trace, "(WebSockets transport) data received: " + message.data); + _this.logger.log(ILogger.LogLevel.Trace, "(WebSockets transport) data received. " + getDataDetail(message.data) + "."); if (_this.onreceive) { _this.onreceive(message.data); } @@ -1498,6 +1620,7 @@ }; WebSocketTransport.prototype.send = function (data) { if (this.webSocket && this.webSocket.readyState === WebSocket.OPEN) { + this.logger.log(ILogger.LogLevel.Trace, "(WebSockets transport) sending data. " + getDataDetail(data) + "."); this.webSocket.send(data); return Promise.resolve(); } @@ -1519,23 +1642,31 @@ this.accessTokenFactory = accessTokenFactory || (function () { return null; }); this.logger = logger; } - ServerSentEventsTransport.prototype.connect = function (url, requestedTransferMode, connection) { + ServerSentEventsTransport.prototype.connect = function (url, transferFormat, connection) { var _this = this; + Utils.Arg.isRequired(url, "url"); + Utils.Arg.isRequired(transferFormat, "transferFormat"); + Utils.Arg.isIn(transferFormat, TransferFormat, "transferFormat"); + Utils.Arg.isRequired(connection, "connection"); if (typeof (EventSource) === "undefined") { - Promise.reject("EventSource not supported by the browser."); + throw new Error("'EventSource' is not supported in your environment."); } + this.logger.log(ILogger.LogLevel.Trace, "(SSE transport) Connecting"); this.url = url; return new Promise(function (resolve, reject) { + if (transferFormat !== TransferFormat.Text) { + reject(new Error("The Server-Sent Events transport only supports the 'Text' transfer format")); + } var token = _this.accessTokenFactory(); if (token) { url += (url.indexOf("?") < 0 ? "?" : "&") + ("access_token=" + encodeURIComponent(token)); } - var eventSource = new EventSource(url); + var eventSource = new EventSource(url, { withCredentials: true }); try { eventSource.onmessage = function (e) { if (_this.onreceive) { try { - _this.logger.log(ILogger.LogLevel.Trace, "(SSE transport) data received: " + e.data); + _this.logger.log(ILogger.LogLevel.Trace, "(SSE transport) data received. " + getDataDetail(e.data) + "."); _this.onreceive(e.data); } catch (error) { @@ -1547,7 +1678,7 @@ } }; eventSource.onerror = function (e) { - reject(); + reject(new Error(e.message || "Error occurred")); // don't report an error if the transport did not start successfully if (_this.eventSource && _this.onclose) { _this.onclose(new Error(e.message || "Error occurred")); @@ -1557,7 +1688,7 @@ _this.logger.log(ILogger.LogLevel.Information, "SSE connected to " + _this.url); _this.eventSource = eventSource; // SSE is a text protocol - resolve(1 /* Text */); + resolve(); }; } catch (e) { @@ -1568,7 +1699,7 @@ ServerSentEventsTransport.prototype.send = function (data) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { - return [2 /*return*/, send(this.httpClient, this.url, this.accessTokenFactory, data)]; + return [2 /*return*/, send(this.logger, "SSE", this.httpClient, this.url, this.accessTokenFactory, data)]; }); }); }; @@ -1589,34 +1720,40 @@ this.logger = logger; this.pollAbort = new AbortController_1.AbortController(); } - LongPollingTransport.prototype.connect = function (url, requestedTransferMode, connection) { + LongPollingTransport.prototype.connect = function (url, transferFormat, connection) { + Utils.Arg.isRequired(url, "url"); + Utils.Arg.isRequired(transferFormat, "transferFormat"); + Utils.Arg.isIn(transferFormat, TransferFormat, "transferFormat"); + Utils.Arg.isRequired(connection, "connection"); this.url = url; + this.logger.log(ILogger.LogLevel.Trace, "(LongPolling transport) Connecting"); // Set a flag indicating we have inherent keep-alive in this transport. connection.features.inherentKeepAlive = true; - if (requestedTransferMode === 2 /* Binary */ && (typeof new XMLHttpRequest().responseType !== "string")) { + if (transferFormat === TransferFormat.Binary && (typeof new XMLHttpRequest().responseType !== "string")) { // This will work if we fix: https://github.com/aspnet/SignalR/issues/742 throw new Error("Binary protocols over XmlHttpRequest not implementing advanced features are not supported."); } - this.poll(this.url, requestedTransferMode); - return Promise.resolve(requestedTransferMode); + this.poll(this.url, transferFormat); + return Promise.resolve(); }; - LongPollingTransport.prototype.poll = function (url, transferMode) { + LongPollingTransport.prototype.poll = function (url, transferFormat) { return __awaiter(this, void 0, void 0, function () { var pollOptions, token, pollUrl, response, e_1; return __generator(this, function (_a) { switch (_a.label) { case 0: pollOptions = { - timeout: 120000, abortSignal: this.pollAbort.signal, - headers: new Map(), + headers: {}, + timeout: 90000, }; - if (transferMode === 2 /* Binary */) { + if (transferFormat === TransferFormat.Binary) { pollOptions.responseType = "arraybuffer"; } token = this.accessTokenFactory(); if (token) { - pollOptions.headers.set("Authorization", "Bearer " + token); + // tslint:disable-next-line:no-string-literal + pollOptions.headers["Authorization"] = "Bearer " + token; } _a.label = 1; case 1: @@ -1648,7 +1785,7 @@ else { // Process the response if (response.content) { - this.logger.log(ILogger.LogLevel.Trace, "(LongPolling transport) data received: " + response.content); + this.logger.log(ILogger.LogLevel.Trace, "(LongPolling transport) data received. " + getDataDetail(response.content) + "."); if (this.onreceive) { this.onreceive(response.content); } @@ -1682,7 +1819,7 @@ LongPollingTransport.prototype.send = function (data) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { - return [2 /*return*/, send(this.httpClient, this.url, this.accessTokenFactory, data)]; + return [2 /*return*/, send(this.logger, "LongPolling", this.httpClient, this.url, this.accessTokenFactory, data)]; }); }); }; @@ -1693,23 +1830,34 @@ return LongPollingTransport; }()); exports.LongPollingTransport = LongPollingTransport; - function send(httpClient, url, accessTokenFactory, content) { + function getDataDetail(data) { + var length = null; + if (data instanceof ArrayBuffer) { + length = "Binary data of length " + data.byteLength; + } + else if (typeof data === "string") { + length = "String data of length " + data.length; + } + return length; + } + function send(logger, transportName, httpClient, url, accessTokenFactory, content) { return __awaiter(this, void 0, void 0, function () { - var headers, token; - return __generator(this, function (_a) { - switch (_a.label) { + var headers, token, response, _a; + return __generator(this, function (_b) { + switch (_b.label) { case 0: token = accessTokenFactory(); if (token) { - headers = new Map(); - headers.set("Authorization", "Bearer " + accessTokenFactory()); + headers = (_a = {}, _a["Authorization"] = "Bearer " + accessTokenFactory(), _a); } + logger.log(ILogger.LogLevel.Trace, "(" + transportName + " transport) sending data. " + getDataDetail(content) + "."); return [4 /*yield*/, httpClient.post(url, { content: content, - headers: headers + headers: headers, })]; case 1: - _a.sent(); + response = _b.sent(); + logger.log(ILogger.LogLevel.Trace, "(" + transportName + " transport) request complete. Response status: " + response.statusCode + "."); return [2 /*return*/]; } }); @@ -1720,69 +1868,10 @@ unwrapExports(Transports); var Transports_1 = Transports.TransportType; - var Transports_2 = Transports.WebSocketTransport; - var Transports_3 = Transports.ServerSentEventsTransport; - var Transports_4 = Transports.LongPollingTransport; - - var Loggers = createCommonjsModule(function (module, exports) { - Object.defineProperty(exports, "__esModule", { value: true }); - - var NullLogger = /** @class */ (function () { - function NullLogger() { - } - NullLogger.prototype.log = function (logLevel, message) { - }; - return NullLogger; - }()); - exports.NullLogger = NullLogger; - var ConsoleLogger = /** @class */ (function () { - function ConsoleLogger(minimumLogLevel) { - this.minimumLogLevel = minimumLogLevel; - } - ConsoleLogger.prototype.log = function (logLevel, message) { - if (logLevel >= this.minimumLogLevel) { - switch (logLevel) { - case ILogger.LogLevel.Error: - console.error(ILogger.LogLevel[logLevel] + ": " + message); - break; - case ILogger.LogLevel.Warning: - console.warn(ILogger.LogLevel[logLevel] + ": " + message); - break; - case ILogger.LogLevel.Information: - console.info(ILogger.LogLevel[logLevel] + ": " + message); - break; - default: - console.log(ILogger.LogLevel[logLevel] + ": " + message); - break; - } - } - }; - return ConsoleLogger; - }()); - exports.ConsoleLogger = ConsoleLogger; - var LoggerFactory; - (function (LoggerFactory) { - function createLogger(logging) { - if (logging === undefined) { - return new ConsoleLogger(ILogger.LogLevel.Information); - } - if (logging === null) { - return new NullLogger(); - } - if (logging.log) { - return logging; - } - return new ConsoleLogger(logging); - } - LoggerFactory.createLogger = createLogger; - })(LoggerFactory = exports.LoggerFactory || (exports.LoggerFactory = {})); - - }); - - unwrapExports(Loggers); - var Loggers_1 = Loggers.NullLogger; - var Loggers_2 = Loggers.ConsoleLogger; - var Loggers_3 = Loggers.LoggerFactory; + var Transports_2 = Transports.TransferFormat; + var Transports_3 = Transports.WebSocketTransport; + var Transports_4 = Transports.ServerSentEventsTransport; + var Transports_5 = Transports.LongPollingTransport; var HttpConnection_1 = createCommonjsModule(function (module, exports) { var __awaiter = (commonjsGlobal && commonjsGlobal.__awaiter) || function (thisArg, _arguments, P, generator) { @@ -1825,131 +1914,231 @@ + var HttpConnection = /** @class */ (function () { function HttpConnection(url, options) { if (options === void 0) { options = {}; } this.features = {}; + Utils.Arg.isRequired(url, "url"); this.logger = Loggers.LoggerFactory.createLogger(options.logger); this.baseUrl = this.resolveUrl(url); options = options || {}; options.accessTokenFactory = options.accessTokenFactory || (function () { return null; }); - this.httpClient = options.httpClient || new HttpClient_1.DefaultHttpClient(); + this.httpClient = options.httpClient || new HttpClient_1.DefaultHttpClient(this.logger); this.connectionState = 2 /* Disconnected */; this.options = options; } - HttpConnection.prototype.start = function () { - return __awaiter(this, void 0, void 0, function () { - return __generator(this, function (_a) { - if (this.connectionState !== 2 /* Disconnected */) { - return [2 /*return*/, Promise.reject(new Error("Cannot start a connection that is not in the 'Disconnected' state."))]; - } - this.connectionState = 0 /* Connecting */; - this.startPromise = this.startInternal(); - return [2 /*return*/, this.startPromise]; - }); - }); + HttpConnection.prototype.start = function (transferFormat) { + Utils.Arg.isRequired(transferFormat, "transferFormat"); + Utils.Arg.isIn(transferFormat, Transports.TransferFormat, "transferFormat"); + this.logger.log(ILogger.LogLevel.Trace, "Starting connection with transfer format '" + Transports.TransferFormat[transferFormat] + "'."); + if (this.connectionState !== 2 /* Disconnected */) { + return Promise.reject(new Error("Cannot start a connection that is not in the 'Disconnected' state.")); + } + this.connectionState = 0 /* Connecting */; + this.startPromise = this.startInternal(transferFormat); + return this.startPromise; }; - HttpConnection.prototype.startInternal = function () { + HttpConnection.prototype.startInternal = function (transferFormat) { return __awaiter(this, void 0, void 0, function () { var _this = this; - var headers, token, negotiatePayload, negotiateResponse, requestedTransferMode, _a, e_1; + var token, headers, negotiateResponse, e_1, _a; return __generator(this, function (_b) { switch (_b.label) { case 0: - _b.trys.push([0, 5, , 6]); - if (!(this.options.transport === Transports.TransportType.WebSockets)) return [3 /*break*/, 1]; + _b.trys.push([0, 6, , 7]); + if (!(this.options.transport === Transports.TransportType.WebSockets)) return [3 /*break*/, 2]; // No need to add a connection ID in this case this.url = this.baseUrl; - this.transport = this.createTransport(this.options.transport, [Transports.TransportType[Transports.TransportType.WebSockets]]); - return [3 /*break*/, 3]; + this.transport = this.constructTransport(Transports.TransportType.WebSockets); + // We should just call connect directly in this case. + // No fallback or negotiate in this case. + return [4 /*yield*/, this.transport.connect(this.url, transferFormat, this)]; case 1: - headers = void 0; + // We should just call connect directly in this case. + // No fallback or negotiate in this case. + _b.sent(); + return [3 /*break*/, 5]; + case 2: token = this.options.accessTokenFactory(); + headers = void 0; if (token) { - headers = new Map(); - headers.set("Authorization", "Bearer " + token); + headers = (_a = {}, _a["Authorization"] = "Bearer " + token, _a); } - return [4 /*yield*/, this.httpClient.post(this.resolveNegotiateUrl(this.baseUrl), { - content: "", - headers: headers - })]; - case 2: - negotiatePayload = _b.sent(); - negotiateResponse = JSON.parse(negotiatePayload.content); - this.connectionId = negotiateResponse.connectionId; + return [4 /*yield*/, this.getNegotiationResponse(headers)]; + case 3: + negotiateResponse = _b.sent(); // the user tries to stop the the connection when it is being started - if (this.connectionState == 2 /* Disconnected */) { + if (this.connectionState === 2 /* Disconnected */) { return [2 /*return*/]; } - if (this.connectionId) { - this.url = this.baseUrl + (this.baseUrl.indexOf("?") === -1 ? "?" : "&") + ("id=" + this.connectionId); - this.transport = this.createTransport(this.options.transport, negotiateResponse.availableTransports); - } - _b.label = 3; - case 3: + return [4 /*yield*/, this.createTransport(this.options.transport, negotiateResponse, transferFormat, headers)]; + case 4: + _b.sent(); + _b.label = 5; + case 5: this.transport.onreceive = this.onreceive; this.transport.onclose = function (e) { return _this.stopConnection(true, e); }; - requestedTransferMode = this.features.transferMode === 2 /* Binary */ - ? 2 /* Binary */ - : 1 /* Text */; - _a = this.features; - return [4 /*yield*/, this.transport.connect(this.url, requestedTransferMode, this)]; - case 4: - _a.transferMode = _b.sent(); // only change the state if we were connecting to not overwrite // the state if the connection is already marked as Disconnected this.changeState(0 /* Connecting */, 1 /* Connected */); - return [3 /*break*/, 6]; - case 5: + return [3 /*break*/, 7]; + case 6: e_1 = _b.sent(); - this.logger.log(ILogger.LogLevel.Error, "Failed to start the connection. " + e_1); + this.logger.log(ILogger.LogLevel.Error, "Failed to start the connection: " + e_1); this.connectionState = 2 /* Disconnected */; this.transport = null; throw e_1; + case 7: return [2 /*return*/]; + } + }); + }); + }; + HttpConnection.prototype.getNegotiationResponse = function (headers) { + return __awaiter(this, void 0, void 0, function () { + var negotiateUrl, response, e_2; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + negotiateUrl = this.resolveNegotiateUrl(this.baseUrl); + this.logger.log(ILogger.LogLevel.Trace, "Sending negotiation request: " + negotiateUrl); + _a.label = 1; + case 1: + _a.trys.push([1, 3, , 4]); + return [4 /*yield*/, this.httpClient.post(negotiateUrl, { + content: "", + headers: headers, + })]; + case 2: + response = _a.sent(); + return [2 /*return*/, JSON.parse(response.content)]; + case 3: + e_2 = _a.sent(); + this.logger.log(ILogger.LogLevel.Error, "Failed to complete negotiation with the server: " + e_2); + throw e_2; + case 4: return [2 /*return*/]; + } + }); + }); + }; + HttpConnection.prototype.updateConnectionId = function (negotiateResponse) { + this.connectionId = negotiateResponse.connectionId; + this.url = this.baseUrl + (this.baseUrl.indexOf("?") === -1 ? "?" : "&") + ("id=" + this.connectionId); + }; + HttpConnection.prototype.createTransport = function (requestedTransport, negotiateResponse, requestedTransferFormat, headers) { + return __awaiter(this, void 0, void 0, function () { + var transports, _i, transports_1, endpoint, transport, ex_1; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + this.updateConnectionId(negotiateResponse); + if (!this.isITransport(requestedTransport)) return [3 /*break*/, 2]; + this.logger.log(ILogger.LogLevel.Trace, "Connection was provided an instance of ITransport, using that directly."); + this.transport = requestedTransport; + return [4 /*yield*/, this.transport.connect(this.url, requestedTransferFormat, this)]; + case 1: + _a.sent(); + // only change the state if we were connecting to not overwrite + // the state if the connection is already marked as Disconnected + this.changeState(0 /* Connecting */, 1 /* Connected */); + return [2 /*return*/]; + case 2: + transports = negotiateResponse.availableTransports; + _i = 0, transports_1 = transports; + _a.label = 3; + case 3: + if (!(_i < transports_1.length)) return [3 /*break*/, 9]; + endpoint = transports_1[_i]; + this.connectionState = 0 /* Connecting */; + transport = this.resolveTransport(endpoint, requestedTransport, requestedTransferFormat); + if (!(typeof transport === "number")) return [3 /*break*/, 8]; + this.transport = this.constructTransport(transport); + if (!(negotiateResponse.connectionId === null)) return [3 /*break*/, 5]; + return [4 /*yield*/, this.getNegotiationResponse(headers)]; + case 4: + negotiateResponse = _a.sent(); + this.updateConnectionId(negotiateResponse); + _a.label = 5; + case 5: + _a.trys.push([5, 7, , 8]); + return [4 /*yield*/, this.transport.connect(this.url, requestedTransferFormat, this)]; case 6: - ; + _a.sent(); + this.changeState(0 /* Connecting */, 1 /* Connected */); return [2 /*return*/]; + case 7: + ex_1 = _a.sent(); + this.logger.log(ILogger.LogLevel.Error, "Failed to start the transport '" + Transports.TransportType[transport] + "': " + ex_1); + this.connectionState = 2 /* Disconnected */; + negotiateResponse.connectionId = null; + return [3 /*break*/, 8]; + case 8: + _i++; + return [3 /*break*/, 3]; + case 9: throw new Error("Unable to initialize any of the available transports."); } }); }); }; - HttpConnection.prototype.createTransport = function (transport, availableTransports) { - if ((transport === null || transport === undefined) && availableTransports.length > 0) { - transport = Transports.TransportType[availableTransports[0]]; + HttpConnection.prototype.constructTransport = function (transport) { + switch (transport) { + case Transports.TransportType.WebSockets: + return new Transports.WebSocketTransport(this.options.accessTokenFactory, this.logger); + case Transports.TransportType.ServerSentEvents: + return new Transports.ServerSentEventsTransport(this.httpClient, this.options.accessTokenFactory, this.logger); + case Transports.TransportType.LongPolling: + return new Transports.LongPollingTransport(this.httpClient, this.options.accessTokenFactory, this.logger); + default: + throw new Error("Unknown transport: " + transport + "."); } - if (transport === Transports.TransportType.WebSockets && availableTransports.indexOf(Transports.TransportType[transport]) >= 0) { - return new Transports.WebSocketTransport(this.options.accessTokenFactory, this.logger); - } - if (transport === Transports.TransportType.ServerSentEvents && availableTransports.indexOf(Transports.TransportType[transport]) >= 0) { - return new Transports.ServerSentEventsTransport(this.httpClient, this.options.accessTokenFactory, this.logger); - } - if (transport === Transports.TransportType.LongPolling && availableTransports.indexOf(Transports.TransportType[transport]) >= 0) { - return new Transports.LongPollingTransport(this.httpClient, this.options.accessTokenFactory, this.logger); + }; + HttpConnection.prototype.resolveTransport = function (endpoint, requestedTransport, requestedTransferFormat) { + var transport = Transports.TransportType[endpoint.transport]; + if (transport === null || transport === undefined) { + this.logger.log(ILogger.LogLevel.Trace, "Skipping transport '" + endpoint.transport + "' because it is not supported by this client."); } - if (this.isITransport(transport)) { - return transport; + else { + var transferFormats = endpoint.transferFormats.map(function (s) { return Transports.TransferFormat[s]; }); + if (!requestedTransport || transport === requestedTransport) { + if (transferFormats.indexOf(requestedTransferFormat) >= 0) { + if ((transport === Transports.TransportType.WebSockets && typeof WebSocket === "undefined") || + (transport === Transports.TransportType.ServerSentEvents && typeof EventSource === "undefined")) { + this.logger.log(ILogger.LogLevel.Trace, "Skipping transport '" + Transports.TransportType[transport] + "' because it is not supported in your environment.'"); + } + else { + this.logger.log(ILogger.LogLevel.Trace, "Selecting transport '" + Transports.TransportType[transport] + "'"); + return transport; + } + } + else { + this.logger.log(ILogger.LogLevel.Trace, "Skipping transport '" + Transports.TransportType[transport] + "' because it does not support the requested transfer format '" + Transports.TransferFormat[requestedTransferFormat] + "'."); + } + } + else { + this.logger.log(ILogger.LogLevel.Trace, "Skipping transport '" + Transports.TransportType[transport] + "' because it was disabled by the client."); + } } - throw new Error("No available transports found."); + return null; }; HttpConnection.prototype.isITransport = function (transport) { return typeof (transport) === "object" && "connect" in transport; }; HttpConnection.prototype.changeState = function (from, to) { - if (this.connectionState == from) { + if (this.connectionState === from) { this.connectionState = to; return true; } return false; }; HttpConnection.prototype.send = function (data) { - if (this.connectionState != 1 /* Connected */) { - throw new Error("Cannot send data if the connection is not in the 'Connected' State"); + if (this.connectionState !== 1 /* Connected */) { + throw new Error("Cannot send data if the connection is not in the 'Connected' State."); } return this.transport.send(data); }; HttpConnection.prototype.stop = function (error) { return __awaiter(this, void 0, void 0, function () { - var previousState, e_2; + var previousState, e_3; return __generator(this, function (_a) { switch (_a.label) { case 0: @@ -1963,10 +2152,10 @@ _a.sent(); return [3 /*break*/, 4]; case 3: - e_2 = _a.sent(); + e_3 = _a.sent(); return [3 /*break*/, 4]; case 4: - this.stopConnection(/*raiseClosed*/ previousState == 1 /* Connected */, error); + this.stopConnection(/*raiseClosed*/ previousState === 1 /* Connected */, error); return [2 /*return*/]; } }); @@ -1993,7 +2182,7 @@ if (url.lastIndexOf("https://", 0) === 0 || url.lastIndexOf("http://", 0) === 0) { return url; } - if (typeof window === 'undefined' || !window || !window.document) { + if (typeof window === "undefined" || !window || !window.document) { throw new Error("Cannot resolve '" + url + "'."); } var parser = window.document.createElement("a"); @@ -2001,11 +2190,11 @@ var baseUrl = (!parser.protocol || parser.protocol === ":") ? window.document.location.protocol + "//" + (parser.host || window.document.location.host) : parser.protocol + "//" + parser.host; - if (!url || url[0] != '/') { - url = '/' + url; + if (!url || url[0] !== "/") { + url = "/" + url; } var normalizedUrl = baseUrl + url; - this.logger.log(ILogger.LogLevel.Information, "Normalizing '" + url + "' to '" + normalizedUrl + "'"); + this.logger.log(ILogger.LogLevel.Information, "Normalizing '" + url + "' to '" + normalizedUrl + "'."); return normalizedUrl; }; HttpConnection.prototype.resolveNegotiateUrl = function (url) { @@ -2027,66 +2216,6 @@ unwrapExports(HttpConnection_1); var HttpConnection_2 = HttpConnection_1.HttpConnection; - var Observable = createCommonjsModule(function (module, exports) { - Object.defineProperty(exports, "__esModule", { value: true }); - var Subscription = /** @class */ (function () { - function Subscription(subject, observer) { - this.subject = subject; - this.observer = observer; - } - Subscription.prototype.dispose = function () { - var index = this.subject.observers.indexOf(this.observer); - if (index > -1) { - this.subject.observers.splice(index, 1); - } - if (this.subject.observers.length === 0) { - this.subject.cancelCallback().catch(function (_) { }); - } - }; - return Subscription; - }()); - exports.Subscription = Subscription; - var Subject = /** @class */ (function () { - function Subject(cancelCallback) { - this.observers = []; - this.cancelCallback = cancelCallback; - } - Subject.prototype.next = function (item) { - for (var _i = 0, _a = this.observers; _i < _a.length; _i++) { - var observer = _a[_i]; - observer.next(item); - } - }; - Subject.prototype.error = function (err) { - for (var _i = 0, _a = this.observers; _i < _a.length; _i++) { - var observer = _a[_i]; - if (observer.error) { - observer.error(err); - } - } - }; - Subject.prototype.complete = function () { - for (var _i = 0, _a = this.observers; _i < _a.length; _i++) { - var observer = _a[_i]; - if (observer.complete) { - observer.complete(); - } - } - }; - Subject.prototype.subscribe = function (observer) { - this.observers.push(observer); - return new Subscription(this, observer); - }; - return Subject; - }()); - exports.Subject = Subject; - - }); - - unwrapExports(Observable); - var Observable_1 = Observable.Subscription; - var Observable_2 = Observable.Subject; - var TextMessageFormat_1 = createCommonjsModule(function (module, exports) { Object.defineProperty(exports, "__esModule", { value: true }); var TextMessageFormat = /** @class */ (function () { @@ -2096,14 +2225,15 @@ return "" + output + TextMessageFormat.RecordSeparator; }; TextMessageFormat.parse = function (input) { - if (input[input.length - 1] != TextMessageFormat.RecordSeparator) { + if (input[input.length - 1] !== TextMessageFormat.RecordSeparator) { throw new Error("Message is incomplete."); } var messages = input.split(TextMessageFormat.RecordSeparator); messages.pop(); return messages; }; - TextMessageFormat.RecordSeparator = String.fromCharCode(0x1e); + TextMessageFormat.RecordSeparatorCode = 0x1e; + TextMessageFormat.RecordSeparator = String.fromCharCode(TextMessageFormat.RecordSeparatorCode); return TextMessageFormat; }()); exports.TextMessageFormat = TextMessageFormat; @@ -2116,27 +2246,86 @@ var JsonHubProtocol_1 = createCommonjsModule(function (module, exports) { Object.defineProperty(exports, "__esModule", { value: true }); + + + exports.JSON_HUB_PROTOCOL_NAME = "json"; var JsonHubProtocol = /** @class */ (function () { function JsonHubProtocol() { this.name = exports.JSON_HUB_PROTOCOL_NAME; - this.type = 1 /* Text */; + this.version = 1; + this.transferFormat = Transports.TransferFormat.Text; } - JsonHubProtocol.prototype.parseMessages = function (input) { + JsonHubProtocol.prototype.parseMessages = function (input, logger) { if (!input) { return []; } + if (logger === null) { + logger = new Loggers.NullLogger(); + } // Parse the messages var messages = TextMessageFormat_1.TextMessageFormat.parse(input); var hubMessages = []; - for (var i = 0; i < messages.length; ++i) { - hubMessages.push(JSON.parse(messages[i])); + for (var _i = 0, messages_1 = messages; _i < messages_1.length; _i++) { + var message = messages_1[_i]; + var parsedMessage = JSON.parse(message); + if (typeof parsedMessage.type !== "number") { + throw new Error("Invalid payload."); + } + switch (parsedMessage.type) { + case 1 /* Invocation */: + this.isInvocationMessage(parsedMessage); + break; + case 2 /* StreamItem */: + this.isStreamItemMessage(parsedMessage); + break; + case 3 /* Completion */: + this.isCompletionMessage(parsedMessage); + break; + case 6 /* Ping */: + // Single value, no need to validate + break; + case 7 /* Close */: + // All optional values, no need to validate + break; + default: + // Future protocol changes can add message types, old clients can ignore them + logger.log(ILogger.LogLevel.Information, "Unknown message type '" + parsedMessage.type + "' ignored."); + continue; + } + hubMessages.push(parsedMessage); } return hubMessages; }; JsonHubProtocol.prototype.writeMessage = function (message) { return TextMessageFormat_1.TextMessageFormat.write(JSON.stringify(message)); }; + JsonHubProtocol.prototype.isInvocationMessage = function (message) { + this.assertNotEmptyString(message.target, "Invalid payload for Invocation message."); + if (message.invocationId !== undefined) { + this.assertNotEmptyString(message.invocationId, "Invalid payload for Invocation message."); + } + }; + JsonHubProtocol.prototype.isStreamItemMessage = function (message) { + this.assertNotEmptyString(message.invocationId, "Invalid payload for StreamItem message."); + if (message.item === undefined) { + throw new Error("Invalid payload for StreamItem message."); + } + }; + JsonHubProtocol.prototype.isCompletionMessage = function (message) { + if (message.result && message.error) { + throw new Error("Invalid payload for Completion message."); + } + if (!message.result && message.error) { + this.assertNotEmptyString(message.error, "Invalid payload for Completion message."); + } + this.assertNotEmptyString(message.invocationId, "Invalid payload for Completion message."); + }; + JsonHubProtocol.prototype.assertNotEmptyString = function (value, errorMessage) { + if (typeof value !== "string" || value === "") { + throw new Error(errorMessage); + } + }; return JsonHubProtocol; }()); exports.JsonHubProtocol = JsonHubProtocol; @@ -2147,58 +2336,65 @@ var JsonHubProtocol_2 = JsonHubProtocol_1.JSON_HUB_PROTOCOL_NAME; var JsonHubProtocol_3 = JsonHubProtocol_1.JsonHubProtocol; - var Base64EncodedHubProtocol_1 = createCommonjsModule(function (module, exports) { + var Observable = createCommonjsModule(function (module, exports) { Object.defineProperty(exports, "__esModule", { value: true }); - var Base64EncodedHubProtocol = /** @class */ (function () { - function Base64EncodedHubProtocol(protocol) { - this.wrappedProtocol = protocol; - this.name = this.wrappedProtocol.name; - this.type = 1 /* Text */; - } - Base64EncodedHubProtocol.prototype.parseMessages = function (input) { - // The format of the message is `size:message;` - var pos = input.indexOf(":"); - if (pos == -1 || input[input.length - 1] != ';') { - throw new Error("Invalid payload."); - } - var lenStr = input.substring(0, pos); - if (!/^[0-9]+$/.test(lenStr)) { - throw new Error("Invalid length: '" + lenStr + "'"); - } - var messageSize = parseInt(lenStr, 10); - // 2 accounts for ':' after message size and trailing ';' - if (messageSize != input.length - pos - 2) { - throw new Error("Invalid message size."); - } - var encodedMessage = input.substring(pos + 1, input.length - 1); - // atob/btoa are browsers APIs but they can be polyfilled. If this becomes problematic we can use - // base64-js module - var s = atob(encodedMessage); - var payload = new Uint8Array(s.length); - for (var i = 0; i < payload.length; i++) { - payload[i] = s.charCodeAt(i); - } - return this.wrappedProtocol.parseMessages(payload.buffer); - }; - Base64EncodedHubProtocol.prototype.writeMessage = function (message) { - var payload = new Uint8Array(this.wrappedProtocol.writeMessage(message)); - var s = ""; - for (var i = 0; i < payload.byteLength; i++) { - s += String.fromCharCode(payload[i]); - } - // atob/btoa are browsers APIs but they can be polyfilled. If this becomes problematic we can use - // base64-js module - var encodedMessage = btoa(s); - return encodedMessage.length.toString() + ":" + encodedMessage + ";"; - }; - return Base64EncodedHubProtocol; + var Subscription = /** @class */ (function () { + function Subscription(subject, observer) { + this.subject = subject; + this.observer = observer; + } + Subscription.prototype.dispose = function () { + var index = this.subject.observers.indexOf(this.observer); + if (index > -1) { + this.subject.observers.splice(index, 1); + } + if (this.subject.observers.length === 0) { + this.subject.cancelCallback().catch(function (_) { }); + } + }; + return Subscription; + }()); + exports.Subscription = Subscription; + var Subject = /** @class */ (function () { + function Subject(cancelCallback) { + this.observers = []; + this.cancelCallback = cancelCallback; + } + Subject.prototype.next = function (item) { + for (var _i = 0, _a = this.observers; _i < _a.length; _i++) { + var observer = _a[_i]; + observer.next(item); + } + }; + Subject.prototype.error = function (err) { + for (var _i = 0, _a = this.observers; _i < _a.length; _i++) { + var observer = _a[_i]; + if (observer.error) { + observer.error(err); + } + } + }; + Subject.prototype.complete = function () { + for (var _i = 0, _a = this.observers; _i < _a.length; _i++) { + var observer = _a[_i]; + if (observer.complete) { + observer.complete(); + } + } + }; + Subject.prototype.subscribe = function (observer) { + this.observers.push(observer); + return new Subscription(this, observer); + }; + return Subject; }()); - exports.Base64EncodedHubProtocol = Base64EncodedHubProtocol; + exports.Subject = Subject; }); - unwrapExports(Base64EncodedHubProtocol_1); - var Base64EncodedHubProtocol_2 = Base64EncodedHubProtocol_1.Base64EncodedHubProtocol; + unwrapExports(Observable); + var Observable_1 = Observable.Subscription; + var Observable_2 = Observable.Subject; var HubConnection_1 = createCommonjsModule(function (module, exports) { var __awaiter = (commonjsGlobal && commonjsGlobal.__awaiter) || function (thisArg, _arguments, P, generator) { @@ -2244,7 +2440,6 @@ - var DEFAULT_TIMEOUT_IN_MS = 30 * 1000; var HubConnection = /** @class */ (function () { function HubConnection(urlOrConnection, options) { @@ -2252,6 +2447,7 @@ var _this = this; options = options || {}; this.timeoutInMilliseconds = options.timeoutInMilliseconds || DEFAULT_TIMEOUT_IN_MS; + this.protocol = options.protocol || new JsonHubProtocol_1.JsonHubProtocol(); if (typeof urlOrConnection === "string") { this.connection = new HttpConnection_1.HttpConnection(urlOrConnection, options); } @@ -2259,46 +2455,107 @@ this.connection = urlOrConnection; } this.logger = Loggers.LoggerFactory.createLogger(options.logger); - this.protocol = options.protocol || new JsonHubProtocol_1.JsonHubProtocol(); this.connection.onreceive = function (data) { return _this.processIncomingData(data); }; this.connection.onclose = function (error) { return _this.connectionClosed(error); }; - this.callbacks = new Map(); - this.methods = new Map(); + this.callbacks = {}; + this.methods = {}; this.closedCallbacks = []; this.id = 0; } HubConnection.prototype.processIncomingData = function (data) { - if (this.timeoutHandle !== undefined) { - clearTimeout(this.timeoutHandle); - } - // Parse the messages - var messages = this.protocol.parseMessages(data); - for (var i = 0; i < messages.length; ++i) { - var message = messages[i]; - switch (message.type) { - case 1 /* Invocation */: - this.invokeClientMethod(message); - break; - case 2 /* StreamItem */: - case 3 /* Completion */: - var callback = this.callbacks.get(message.invocationId); - if (callback != null) { - if (message.type === 3 /* Completion */) { - this.callbacks.delete(message.invocationId); + this.cleanupTimeout(); + if (!this.receivedHandshakeResponse) { + data = this.processHandshakeResponse(data); + this.receivedHandshakeResponse = true; + } + // Data may have all been read when processing handshake response + if (data) { + // Parse the messages + var messages = this.protocol.parseMessages(data, this.logger); + for (var _i = 0, messages_1 = messages; _i < messages_1.length; _i++) { + var message = messages_1[_i]; + switch (message.type) { + case 1 /* Invocation */: + this.invokeClientMethod(message); + break; + case 2 /* StreamItem */: + case 3 /* Completion */: + var callback = this.callbacks[message.invocationId]; + if (callback != null) { + if (message.type === 3 /* Completion */) { + delete this.callbacks[message.invocationId]; + } + callback(message); } - callback(message); - } - break; - case 6 /* Ping */: - // Don't care about pings - break; - default: - this.logger.log(ILogger.LogLevel.Warning, "Invalid message type: " + data); - break; + break; + case 6 /* Ping */: + // Don't care about pings + break; + case 7 /* Close */: + this.logger.log(ILogger.LogLevel.Information, "Close message received from server."); + this.connection.stop(message.error ? new Error("Server returned an error on close: " + message.error) : null); + break; + default: + this.logger.log(ILogger.LogLevel.Warning, "Invalid message type: " + message.type); + break; + } } } this.configureTimeout(); }; + HubConnection.prototype.processHandshakeResponse = function (data) { + var responseMessage; + var messageData; + var remainingData; + try { + if (data instanceof ArrayBuffer) { + // Format is binary but still need to read JSON text from handshake response + var binaryData = new Uint8Array(data); + var separatorIndex = binaryData.indexOf(TextMessageFormat_1.TextMessageFormat.RecordSeparatorCode); + if (separatorIndex === -1) { + throw new Error("Message is incomplete."); + } + // content before separator is handshake response + // optional content after is additional messages + var responseLength = separatorIndex + 1; + messageData = String.fromCharCode.apply(null, binaryData.slice(0, responseLength)); + remainingData = (binaryData.byteLength > responseLength) ? binaryData.slice(responseLength).buffer : null; + } + else { + var textData = data; + var separatorIndex = textData.indexOf(TextMessageFormat_1.TextMessageFormat.RecordSeparator); + if (separatorIndex === -1) { + throw new Error("Message is incomplete."); + } + // content before separator is handshake response + // optional content after is additional messages + var responseLength = separatorIndex + 1; + messageData = textData.substring(0, responseLength); + remainingData = (textData.length > responseLength) ? textData.substring(responseLength) : null; + } + // At this point we should have just the single handshake message + var messages = TextMessageFormat_1.TextMessageFormat.parse(messageData); + responseMessage = JSON.parse(messages[0]); + } + catch (e) { + var message = "Error parsing handshake response: " + e; + this.logger.log(ILogger.LogLevel.Error, message); + var error = new Error(message); + this.connection.stop(error); + throw error; + } + if (responseMessage.error) { + var message = "Server returned handshake error: " + responseMessage.error; + this.logger.log(ILogger.LogLevel.Error, message); + this.connection.stop(new Error(message)); + } + else { + this.logger.log(ILogger.LogLevel.Trace, "Server handshake complete."); + } + // multiple messages could have arrived with handshake + // return additional data to be parsed as usual, or null if all parsed + return remainingData; + }; HubConnection.prototype.configureTimeout = function () { var _this = this; if (!this.connection.features || !this.connection.features.inherentKeepAlive) { @@ -2313,7 +2570,7 @@ }; HubConnection.prototype.invokeClientMethod = function (invocationMessage) { var _this = this; - var methods = this.methods.get(invocationMessage.target.toLowerCase()); + var methods = this.methods[invocationMessage.target.toLowerCase()]; if (methods) { methods.forEach(function (m) { return m.apply(_this, invocationMessage.arguments); }); if (invocationMessage.invocationId) { @@ -2329,34 +2586,35 @@ }; HubConnection.prototype.connectionClosed = function (error) { var _this = this; - this.callbacks.forEach(function (callback) { - callback(undefined, error ? error : new Error("Invocation canceled due to connection being closed.")); - }); - this.callbacks.clear(); - this.closedCallbacks.forEach(function (c) { return c.apply(_this, [error]); }); + var callbacks = this.callbacks; + this.callbacks = {}; + Object.keys(callbacks) + .forEach(function (key) { + var callback = callbacks[key]; + callback(undefined, error ? error : new Error("Invocation canceled due to connection being closed.")); + }); this.cleanupTimeout(); + this.closedCallbacks.forEach(function (c) { return c.apply(_this, [error]); }); }; HubConnection.prototype.start = function () { return __awaiter(this, void 0, void 0, function () { - var requestedTransferMode, actualTransferMode; return __generator(this, function (_a) { switch (_a.label) { case 0: - requestedTransferMode = (this.protocol.type === 2 /* Binary */) - ? 2 /* Binary */ - : 1 /* Text */; - this.connection.features.transferMode = requestedTransferMode; - return [4 /*yield*/, this.connection.start()]; + this.logger.log(ILogger.LogLevel.Trace, "Starting HubConnection."); + this.receivedHandshakeResponse = false; + return [4 /*yield*/, this.connection.start(this.protocol.transferFormat)]; case 1: _a.sent(); - actualTransferMode = this.connection.features.transferMode; - return [4 /*yield*/, this.connection.send(TextMessageFormat_1.TextMessageFormat.write(JSON.stringify({ protocol: this.protocol.name })))]; + this.logger.log(ILogger.LogLevel.Trace, "Sending handshake request."); + // Handshake request is always JSON + return [4 /*yield*/, this.connection.send(TextMessageFormat_1.TextMessageFormat.write(JSON.stringify({ protocol: this.protocol.name, version: this.protocol.version })))]; case 2: + // Handshake request is always JSON _a.sent(); this.logger.log(ILogger.LogLevel.Information, "Using HubProtocol '" + this.protocol.name + "'."); - if (requestedTransferMode === 2 /* Binary */ && actualTransferMode === 1 /* Text */) { - this.protocol = new Base64EncodedHubProtocol_1.Base64EncodedHubProtocol(this.protocol); - } + // defensively cleanup timeout in case we receive a message from the server before we finish start + this.cleanupTimeout(); this.configureTimeout(); return [2 /*return*/]; } @@ -2364,6 +2622,7 @@ }); }; HubConnection.prototype.stop = function () { + this.logger.log(ILogger.LogLevel.Trace, "Stopping HubConnection."); this.cleanupTimeout(); return this.connection.stop(); }; @@ -2376,33 +2635,32 @@ var invocationDescriptor = this.createStreamInvocation(methodName, args); var subject = new Observable.Subject(function () { var cancelInvocation = _this.createCancelInvocation(invocationDescriptor.invocationId); - var message = _this.protocol.writeMessage(cancelInvocation); - _this.callbacks.delete(invocationDescriptor.invocationId); - return _this.connection.send(message); + var cancelMessage = _this.protocol.writeMessage(cancelInvocation); + delete _this.callbacks[invocationDescriptor.invocationId]; + return _this.connection.send(cancelMessage); }); - this.callbacks.set(invocationDescriptor.invocationId, function (invocationEvent, error) { + this.callbacks[invocationDescriptor.invocationId] = function (invocationEvent, error) { if (error) { subject.error(error); return; } if (invocationEvent.type === 3 /* Completion */) { - var completionMessage = invocationEvent; - if (completionMessage.error) { - subject.error(new Error(completionMessage.error)); + if (invocationEvent.error) { + subject.error(new Error(invocationEvent.error)); } else { subject.complete(); } } else { - subject.next(invocationEvent.item); + subject.next((invocationEvent.item)); } - }); + }; var message = this.protocol.writeMessage(invocationDescriptor); this.connection.send(message) .catch(function (e) { subject.error(e); - _this.callbacks.delete(invocationDescriptor.invocationId); + delete _this.callbacks[invocationDescriptor.invocationId]; }); return subject; }; @@ -2423,7 +2681,7 @@ } var invocationDescriptor = this.createInvocation(methodName, args, false); var p = new Promise(function (resolve, reject) { - _this.callbacks.set(invocationDescriptor.invocationId, function (invocationEvent, error) { + _this.callbacks[invocationDescriptor.invocationId] = function (invocationEvent, error) { if (error) { reject(error); return; @@ -2440,38 +2698,50 @@ else { reject(new Error("Unexpected message type: " + invocationEvent.type)); } - }); + }; var message = _this.protocol.writeMessage(invocationDescriptor); _this.connection.send(message) .catch(function (e) { reject(e); - _this.callbacks.delete(invocationDescriptor.invocationId); + delete _this.callbacks[invocationDescriptor.invocationId]; }); }); return p; }; - HubConnection.prototype.on = function (methodName, method) { - if (!methodName || !method) { + HubConnection.prototype.on = function (methodName, newMethod) { + if (!methodName || !newMethod) { return; } methodName = methodName.toLowerCase(); - if (!this.methods.has(methodName)) { - this.methods.set(methodName, []); + if (!this.methods[methodName]) { + this.methods[methodName] = []; } - this.methods.get(methodName).push(method); + // Preventing adding the same handler multiple times. + if (this.methods[methodName].indexOf(newMethod) !== -1) { + return; + } + this.methods[methodName].push(newMethod); }; HubConnection.prototype.off = function (methodName, method) { - if (!methodName || !method) { + if (!methodName) { return; } methodName = methodName.toLowerCase(); - var handlers = this.methods.get(methodName); + var handlers = this.methods[methodName]; if (!handlers) { return; } - var removeIdx = handlers.indexOf(method); - if (removeIdx != -1) { - handlers.splice(removeIdx, 1); + if (method) { + var removeIdx = handlers.indexOf(method); + if (removeIdx !== -1) { + handlers.splice(removeIdx, 1); + if (handlers.length === 0) { + delete this.methods[methodName]; + } + } + } + else { + delete this.methods[methodName]; } }; HubConnection.prototype.onclose = function (callback) { @@ -2487,19 +2757,19 @@ HubConnection.prototype.createInvocation = function (methodName, args, nonblocking) { if (nonblocking) { return { - type: 1 /* Invocation */, - target: methodName, arguments: args, + target: methodName, + type: 1 /* Invocation */, }; } else { var id = this.id; this.id++; return { - type: 1 /* Invocation */, + arguments: args, invocationId: id.toString(), target: methodName, - arguments: args, + type: 1 /* Invocation */, }; } }; @@ -2507,16 +2777,16 @@ var id = this.id; this.id++; return { - type: 4 /* StreamInvocation */, + arguments: args, invocationId: id.toString(), target: methodName, - arguments: args, + type: 4 /* StreamInvocation */, }; }; HubConnection.prototype.createCancelInvocation = function (id) { return { - type: 5 /* CancelInvocation */, invocationId: id, + type: 5 /* CancelInvocation */, }; }; return HubConnection; diff --git a/dist/signalR-netcore.min.js b/dist/signalR-netcore.min.js index a7136f37..4df24ce7 100644 --- a/dist/signalR-netcore.min.js +++ b/dist/signalR-netcore.min.js @@ -1,2 +1,2 @@ -!function(a,b){"object"==typeof exports&&"undefined"!=typeof module?module.exports=b():"function"==typeof define&&define.amd?define(b):a.signalR=b()}(this,function(){"use strict";function a(){throw new Error("Dynamic requires are not currently supported by rollup-plugin-commonjs")}function b(a){return a&&a.__esModule&&Object.prototype.hasOwnProperty.call(a,"default")?a.default:a}function c(a,b){return b={exports:{}},a(b,b.exports),b.exports}var d="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},e=(c(function(b,c){!function(a,c){b.exports=c()}(d,function(){function b(a){var b=typeof a;return null!==a&&("object"===b||"function"===b)}function c(a){return"function"==typeof a}function e(a){U=a}function f(a){V=a}function g(){return function(){return process.nextTick(l)}}function h(){return"undefined"!=typeof T?function(){T(l)}:k()}function i(){var a=0,b=new Y(l),c=document.createTextNode("");return b.observe(c,{characterData:!0}),function(){c.data=a=++a%2}}function j(){var a=new MessageChannel;return a.port1.onmessage=l,function(){return a.port2.postMessage(0)}}function k(){var a=setTimeout;return function(){return a(l,1)}}function l(){for(var a=0;a=200&&d.status<300?b(new g(d.status,d.statusText,d.response||d.responseText)):c(new e.HttpError(d.statusText,d.status))},d.onerror=function(){c(new e.HttpError(d.statusText,d.status))},d.ontimeout=function(){c(new e.TimeoutError)},d.send(a.content||"")})},b}(h);b.DefaultHttpClient=i}));b(f);var g=(f.HttpResponse,f.HttpClient,f.DefaultHttpClient,c(function(a,b){Object.defineProperty(b,"__esModule",{value:!0});var c;!function(a){a[a.Trace=0]="Trace",a[a.Information=1]="Information",a[a.Warning=2]="Warning",a[a.Error=3]="Error",a[a.None=4]="None"}(c=b.LogLevel||(b.LogLevel={}))}));b(g);var h=(g.LogLevel,c(function(a,b){Object.defineProperty(b,"__esModule",{value:!0});var c=function(){function a(){this.isAborted=!1}return a.prototype.abort=function(){this.isAborted||(this.isAborted=!0,this.onabort&&this.onabort())},Object.defineProperty(a.prototype,"signal",{get:function(){return this},enumerable:!0,configurable:!0}),Object.defineProperty(a.prototype,"aborted",{get:function(){return this.isAborted},enumerable:!0,configurable:!0}),a}();b.AbortController=c}));b(h);var i=(h.AbortController,c(function(a,b){function c(a,b,c,d){return f(this,void 0,void 0,function(){var e,f;return i(this,function(g){switch(g.label){case 0:return f=c(),f&&(e=new Map,e.set("Authorization","Bearer "+c())),[4,a.post(b,{content:d,headers:e})];case 1:return g.sent(),[2]}})})}var f=d&&d.__awaiter||function(a,b,c,d){return new(c||(c=Promise))(function(e,f){function g(a){try{i(d.next(a))}catch(a){f(a)}}function h(a){try{i(d.throw(a))}catch(a){f(a)}}function i(a){a.done?e(a.value):new c(function(b){b(a.value)}).then(g,h)}i((d=d.apply(a,b||[])).next())})},i=d&&d.__generator||function(a,b){function c(a){return function(b){return d([a,b])}}function d(c){if(e)throw new TypeError("Generator is already executing.");for(;i;)try{if(e=1,f&&(g=f[2&c[0]?"return":c[0]?"throw":"next"])&&!(g=g.call(f,c[1])).done)return g;switch(f=0,g&&(c=[0,g.value]),c[0]){case 0:case 1:g=c;break;case 4:return i.label++,{value:c[1],done:!1};case 5:i.label++,f=c[1],c=[0];continue;case 7:c=i.ops.pop(),i.trys.pop();continue;default:if(g=i.trys,!(g=g.length>0&&g[g.length-1])&&(6===c[0]||2===c[0])){i=0;continue}if(3===c[0]&&(!g||c[1]>g[0]&&c[1]=this.minimumLogLevel)switch(a){case g.LogLevel.Error:console.error(g.LogLevel[a]+": "+b);break;case g.LogLevel.Warning:console.warn(g.LogLevel[a]+": "+b);break;case g.LogLevel.Information:console.info(g.LogLevel[a]+": "+b);break;default:console.log(g.LogLevel[a]+": "+b)}},a}();b.ConsoleLogger=d;var e;!function(a){function b(a){return void 0===a?new d(g.LogLevel.Information):null===a?new c:a.log?a:new d(a)}a.createLogger=b}(e=b.LoggerFactory||(b.LoggerFactory={}))}));b(j);var k=(j.NullLogger,j.ConsoleLogger,j.LoggerFactory,c(function(a,b){var c=d&&d.__awaiter||function(a,b,c,d){return new(c||(c=Promise))(function(e,f){function g(a){try{i(d.next(a))}catch(a){f(a)}}function h(a){try{i(d.throw(a))}catch(a){f(a)}}function i(a){a.done?e(a.value):new c(function(b){b(a.value)}).then(g,h)}i((d=d.apply(a,b||[])).next())})},e=d&&d.__generator||function(a,b){function c(a){return function(b){return d([a,b])}}function d(c){if(e)throw new TypeError("Generator is already executing.");for(;i;)try{if(e=1,f&&(g=f[2&c[0]?"return":c[0]?"throw":"next"])&&!(g=g.call(f,c[1])).done)return g;switch(f=0,g&&(c=[0,g.value]),c[0]){case 0:case 1:g=c;break;case 4:return i.label++,{value:c[1],done:!1};case 5:i.label++,f=c[1],c=[0];continue;case 7:c=i.ops.pop(),i.trys.pop();continue;default:if(g=i.trys,!(g=g.length>0&&g[g.length-1])&&(6===c[0]||2===c[0])){i=0;continue}if(3===c[0]&&(!g||c[1]>g[0]&&c[1]0&&(a=i.TransportType[b[0]]),a===i.TransportType.WebSockets&&b.indexOf(i.TransportType[a])>=0)return new i.WebSocketTransport(this.options.accessTokenFactory,this.logger);if(a===i.TransportType.ServerSentEvents&&b.indexOf(i.TransportType[a])>=0)return new i.ServerSentEventsTransport(this.httpClient,this.options.accessTokenFactory,this.logger);if(a===i.TransportType.LongPolling&&b.indexOf(i.TransportType[a])>=0)return new i.LongPollingTransport(this.httpClient,this.options.accessTokenFactory,this.logger);if(this.isITransport(a))return a;throw new Error("No available transports found.")},a.prototype.isITransport=function(a){return"object"==typeof a&&"connect"in a},a.prototype.changeState=function(a,b){return this.connectionState==a&&(this.connectionState=b,!0)},a.prototype.send=function(a){if(1!=this.connectionState)throw new Error("Cannot send data if the connection is not in the 'Connected' State");return this.transport.send(a)},a.prototype.stop=function(a){return c(this,void 0,void 0,function(){var b,c;return e(this,function(d){switch(d.label){case 0:b=this.connectionState,this.connectionState=2,d.label=1;case 1:return d.trys.push([1,3,,4]),[4,this.startPromise];case 2:return d.sent(),[3,4];case 3:return c=d.sent(),[3,4];case 4:return this.stopConnection(1==b,a),[2]}})})},a.prototype.stopConnection=function(a,b){this.transport&&(this.transport.stop(),this.transport=null),b?this.logger.log(g.LogLevel.Error,"Connection disconnected with error '"+b+"'."):this.logger.log(g.LogLevel.Information,"Connection disconnected."),this.connectionState=2,a&&this.onclose&&this.onclose(b)},a.prototype.resolveUrl=function(a){if(0===a.lastIndexOf("https://",0)||0===a.lastIndexOf("http://",0))return a;if("undefined"==typeof window||!window||!window.document)throw new Error("Cannot resolve '"+a+"'.");var b=window.document.createElement("a");b.href=a;var c=b.protocol&&":"!==b.protocol?b.protocol+"//"+b.host:window.document.location.protocol+"//"+(b.host||window.document.location.host);a&&"/"==a[0]||(a="/"+a);var d=c+a;return this.logger.log(g.LogLevel.Information,"Normalizing '"+a+"' to '"+d+"'"),d},a.prototype.resolveNegotiateUrl=function(a){var b=a.indexOf("?"),c=a.substring(0,b===-1?a.length:b);return"/"!==c[c.length-1]&&(c+="/"),c+="negotiate",c+=b===-1?"":a.substring(b)},a}();b.HttpConnection=h}));b(k);var l=(k.HttpConnection,c(function(a,b){Object.defineProperty(b,"__esModule",{value:!0});var c=function(){function a(a,b){this.subject=a,this.observer=b}return a.prototype.dispose=function(){var a=this.subject.observers.indexOf(this.observer);a>-1&&this.subject.observers.splice(a,1),0===this.subject.observers.length&&this.subject.cancelCallback().catch(function(a){})},a}();b.Subscription=c;var d=function(){function a(a){this.observers=[],this.cancelCallback=a}return a.prototype.next=function(a){for(var b=0,c=this.observers;b0&&g[g.length-1])&&(6===c[0]||2===c[0])){i=0;continue}if(3===c[0]&&(!g||c[1]>g[0]&&c[1]=200&&g.status<300?c(new h(g.status,g.statusText,g.response||g.responseText)):d(new e.HttpError(g.statusText,g.status))},g.onerror=function(){b.logger.log(f.LogLevel.Warning,"Error from HTTP request. "+g.status+": "+g.statusText),d(new e.HttpError(g.statusText,g.status))},g.ontimeout=function(){b.logger.log(f.LogLevel.Warning,"Timeout from HTTP request."),d(new e.TimeoutError)},g.send(a.content||"")})},b}(i);b.DefaultHttpClient=j}));b(g);var h=(g.HttpResponse,g.HttpClient,g.DefaultHttpClient,c(function(a,b){Object.defineProperty(b,"__esModule",{value:!0});var c=function(){function a(){}return a.prototype.log=function(a,b){},a}();b.NullLogger=c;var d=function(){function a(a){this.minimumLogLevel=a}return a.prototype.log=function(a,b){if(a>=this.minimumLogLevel)switch(a){case f.LogLevel.Error:console.error(f.LogLevel[a]+": "+b);break;case f.LogLevel.Warning:console.warn(f.LogLevel[a]+": "+b);break;case f.LogLevel.Information:console.info(f.LogLevel[a]+": "+b);break;default:console.log(f.LogLevel[a]+": "+b)}},a}();b.ConsoleLogger=d;var e=function(){function a(){}return a.createLogger=function(a){return void 0===a?new d(f.LogLevel.Information):null===a?new c:a.log?a:new d(a)},a}();b.LoggerFactory=e}));b(h);var i=(h.NullLogger,h.ConsoleLogger,h.LoggerFactory,c(function(a,b){Object.defineProperty(b,"__esModule",{value:!0});var c=function(){function a(){this.isAborted=!1}return a.prototype.abort=function(){this.isAborted||(this.isAborted=!0,this.onabort&&this.onabort())},Object.defineProperty(a.prototype,"signal",{get:function(){return this},enumerable:!0,configurable:!0}),Object.defineProperty(a.prototype,"aborted",{get:function(){return this.isAborted},enumerable:!0,configurable:!0}),a}();b.AbortController=c}));b(i);var j=(i.AbortController,c(function(a,b){Object.defineProperty(b,"__esModule",{value:!0});var c=function(){function a(){}return a.isRequired=function(a,b){if(null===a||void 0===a)throw new Error("The '"+b+"' argument is required.")},a.isIn=function(a,b,c){if(!(a in b))throw new Error("Unknown "+c+" value: "+a+".")},a}();b.Arg=c}));b(j);var k=(j.Arg,c(function(a,b){function c(a){var b=null;return a instanceof ArrayBuffer?b="Binary data of length "+a.byteLength:"string"==typeof a&&(b="String data of length "+a.length),b}function g(a,b,d,e,g,i){return h(this,void 0,void 0,function(){var h,j,l,m;return k(this,function(k){switch(k.label){case 0:return j=g(),j&&(m={},m.Authorization="Bearer "+g(),h=m),a.log(f.LogLevel.Trace,"("+b+" transport) sending data. "+c(i)+"."),[4,d.post(e,{content:i,headers:h})];case 1:return l=k.sent(),a.log(f.LogLevel.Trace,"("+b+" transport) request complete. Response status: "+l.statusCode+"."),[2]}})})}var h=d&&d.__awaiter||function(a,b,c,d){return new(c||(c=Promise))(function(e,f){function g(a){try{i(d.next(a))}catch(a){f(a)}}function h(a){try{i(d.throw(a))}catch(a){f(a)}}function i(a){a.done?e(a.value):new c(function(b){b(a.value)}).then(g,h)}i((d=d.apply(a,b||[])).next())})},k=d&&d.__generator||function(a,b){function c(a){return function(b){return d([a,b])}}function d(c){if(e)throw new TypeError("Generator is already executing.");for(;i;)try{if(e=1,f&&(g=f[2&c[0]?"return":c[0]?"throw":"next"])&&!(g=g.call(f,c[1])).done)return g;switch(f=0,g&&(c=[0,g.value]),c[0]){case 0:case 1:g=c;break;case 4:return i.label++,{value:c[1],done:!1};case 5:i.label++,f=c[1],c=[0];continue;case 7:c=i.ops.pop(),i.trys.pop();continue;default:if(g=i.trys,!(g=g.length>0&&g[g.length-1])&&(6===c[0]||2===c[0])){i=0;continue}if(3===c[0]&&(!g||c[1]>g[0]&&c[1]0&&g[g.length-1])&&(6===c[0]||2===c[0])){i=0;continue}if(3===c[0]&&(!g||c[1]>g[0]&&c[1]=0){if(!(d===k.TransportType.WebSockets&&"undefined"==typeof WebSocket||d===k.TransportType.ServerSentEvents&&"undefined"==typeof EventSource))return this.logger.log(f.LogLevel.Trace,"Selecting transport '"+k.TransportType[d]+"'"),d;this.logger.log(f.LogLevel.Trace,"Skipping transport '"+k.TransportType[d]+"' because it is not supported in your environment.'")}else this.logger.log(f.LogLevel.Trace,"Skipping transport '"+k.TransportType[d]+"' because it does not support the requested transfer format '"+k.TransferFormat[c]+"'.")}return null},a.prototype.isITransport=function(a){return"object"==typeof a&&"connect"in a},a.prototype.changeState=function(a,b){return this.connectionState===a&&(this.connectionState=b,!0)},a.prototype.send=function(a){if(1!==this.connectionState)throw new Error("Cannot send data if the connection is not in the 'Connected' State.");return this.transport.send(a)},a.prototype.stop=function(a){return c(this,void 0,void 0,function(){var b,c;return e(this,function(d){switch(d.label){case 0:b=this.connectionState,this.connectionState=2,d.label=1;case 1:return d.trys.push([1,3,,4]),[4,this.startPromise];case 2:return d.sent(),[3,4];case 3:return c=d.sent(),[3,4];case 4:return this.stopConnection(1===b,a),[2]}})})},a.prototype.stopConnection=function(a,b){this.transport&&(this.transport.stop(),this.transport=null),b?this.logger.log(f.LogLevel.Error,"Connection disconnected with error '"+b+"'."):this.logger.log(f.LogLevel.Information,"Connection disconnected."),this.connectionState=2,a&&this.onclose&&this.onclose(b)},a.prototype.resolveUrl=function(a){if(0===a.lastIndexOf("https://",0)||0===a.lastIndexOf("http://",0))return a;if("undefined"==typeof window||!window||!window.document)throw new Error("Cannot resolve '"+a+"'.");var b=window.document.createElement("a");b.href=a;var c=b.protocol&&":"!==b.protocol?b.protocol+"//"+b.host:window.document.location.protocol+"//"+(b.host||window.document.location.host);a&&"/"===a[0]||(a="/"+a);var d=c+a;return this.logger.log(f.LogLevel.Information,"Normalizing '"+a+"' to '"+d+"'."),d},a.prototype.resolveNegotiateUrl=function(a){var b=a.indexOf("?"),c=a.substring(0,b===-1?a.length:b);return"/"!==c[c.length-1]&&(c+="/"),c+="negotiate",c+=b===-1?"":a.substring(b)},a}();b.HttpConnection=i}));b(l);var m=(l.HttpConnection,c(function(a,b){Object.defineProperty(b,"__esModule",{value:!0});var c=function(){function a(){}return a.write=function(b){return""+b+a.RecordSeparator},a.parse=function(b){if(b[b.length-1]!==a.RecordSeparator)throw new Error("Message is incomplete.");var c=b.split(a.RecordSeparator);return c.pop(),c},a.RecordSeparatorCode=30,a.RecordSeparator=String.fromCharCode(a.RecordSeparatorCode),a}();b.TextMessageFormat=c}));b(m);var n=(m.TextMessageFormat,c(function(a,b){Object.defineProperty(b,"__esModule",{value:!0}),b.JSON_HUB_PROTOCOL_NAME="json";var c=function(){function a(){this.name=b.JSON_HUB_PROTOCOL_NAME,this.version=1,this.transferFormat=k.TransferFormat.Text}return a.prototype.parseMessages=function(a,b){if(!a)return[];null===b&&(b=new h.NullLogger);for(var c=m.TextMessageFormat.parse(a),d=[],e=0,g=c;e-1&&this.subject.observers.splice(a,1),0===this.subject.observers.length&&this.subject.cancelCallback().catch(function(a){})},a}();b.Subscription=c;var d=function(){function a(a){this.observers=[],this.cancelCallback=a}return a.prototype.next=function(a){for(var b=0,c=this.observers;b0&&g[g.length-1])&&(6===c[0]||2===c[0])){i=0;continue}if(3===c[0]&&(!g||c[1]>g[0]&&c[1]h?e.slice(h).buffer:null}else{var i=a,g=i.indexOf(m.TextMessageFormat.RecordSeparator);if(g===-1)throw new Error("Message is incomplete.");var h=g+1;c=i.substring(0,h),d=i.length>h?i.substring(h):null}var j=m.TextMessageFormat.parse(c);b=JSON.parse(j[0])}catch(a){var k="Error parsing handshake response: "+a;this.logger.log(f.LogLevel.Error,k);var l=new Error(k);throw this.connection.stop(l),l}if(b.error){var k="Server returned handshake error: "+b.error;this.logger.log(f.LogLevel.Error,k),this.connection.stop(new Error(k))}else this.logger.log(f.LogLevel.Trace,"Server handshake complete.");return d},a.prototype.configureTimeout=function(){var a=this;this.connection.features&&this.connection.features.inherentKeepAlive||(this.timeoutHandle=setTimeout(function(){return a.serverTimeout()},this.timeoutInMilliseconds))},a.prototype.serverTimeout=function(){this.connection.stop(new Error("Server timeout elapsed without receiving a message from the server."))},a.prototype.invokeClientMethod=function(a){var b=this,c=this.methods[a.target.toLowerCase()];if(c){if(c.forEach(function(c){return c.apply(b,a.arguments)}),a.invocationId){var d="Server requested a response, which is not supported in this version of the client.";this.logger.log(f.LogLevel.Error,d),this.connection.stop(new Error(d))}}else this.logger.log(f.LogLevel.Warning,"No client method with the name '"+a.target+"' found.")},a.prototype.connectionClosed=function(a){var b=this,c=this.callbacks;this.callbacks={},Object.keys(c).forEach(function(b){var d=c[b];d(void 0,a?a:new Error("Invocation canceled due to connection being closed."))}),this.cleanupTimeout(),this.closedCallbacks.forEach(function(c){return c.apply(b,[a])})},a.prototype.start=function(){return c(this,void 0,void 0,function(){return e(this,function(a){switch(a.label){case 0:return this.logger.log(f.LogLevel.Trace,"Starting HubConnection."),this.receivedHandshakeResponse=!1,[4,this.connection.start(this.protocol.transferFormat)];case 1:return a.sent(),this.logger.log(f.LogLevel.Trace,"Sending handshake request."),[4,this.connection.send(m.TextMessageFormat.write(JSON.stringify({protocol:this.protocol.name,version:this.protocol.version})))];case 2:return a.sent(),this.logger.log(f.LogLevel.Information,"Using HubProtocol '"+this.protocol.name+"'."),this.cleanupTimeout(),this.configureTimeout(),[2]}})})},a.prototype.stop=function(){return this.logger.log(f.LogLevel.Trace,"Stopping HubConnection."),this.cleanupTimeout(),this.connection.stop()},a.prototype.stream=function(a){for(var b=this,c=[],d=1;d= this.minimumLogLevel) { + switch (logLevel) { + case ILogger.LogLevel.Error: + console.error(ILogger.LogLevel[logLevel] + ": " + message); + break; + case ILogger.LogLevel.Warning: + console.warn(ILogger.LogLevel[logLevel] + ": " + message); + break; + case ILogger.LogLevel.Information: + console.info(ILogger.LogLevel[logLevel] + ": " + message); + break; + default: + console.log(ILogger.LogLevel[logLevel] + ": " + message); + break; + } + } + }; + return ConsoleLogger; + }()); + exports.ConsoleLogger = ConsoleLogger; + var LoggerFactory = /** @class */ (function () { + function LoggerFactory() { + } + LoggerFactory.createLogger = function (logging) { + if (logging === undefined) { + return new ConsoleLogger(ILogger.LogLevel.Information); + } + if (logging === null) { + return new NullLogger(); + } + if (logging.log) { + return logging; + } + return new ConsoleLogger(logging); + }; + return LoggerFactory; + }()); + exports.LoggerFactory = LoggerFactory; }); - unwrapExports(ILogger); - var ILogger_1 = ILogger.LogLevel; + unwrapExports(Loggers); + var Loggers_1 = Loggers.NullLogger; + var Loggers_2 = Loggers.ConsoleLogger; + var Loggers_3 = Loggers.LoggerFactory; var AbortController_1 = createCommonjsModule(function (module, exports) { Object.defineProperty(exports, "__esModule", { value: true }); @@ -1406,6 +1489,31 @@ unwrapExports(AbortController_1); var AbortController_2 = AbortController_1.AbortController; + var Utils = createCommonjsModule(function (module, exports) { + Object.defineProperty(exports, "__esModule", { value: true }); + var Arg = /** @class */ (function () { + function Arg() { + } + Arg.isRequired = function (val, name) { + if (val === null || val === undefined) { + throw new Error("The '" + name + "' argument is required."); + } + }; + Arg.isIn = function (val, values, name) { + // TypeScript enums have keys for **both** the name and the value of each enum member on the type itself. + if (!(val in values)) { + throw new Error("Unknown " + name + " value: " + val + "."); + } + }; + return Arg; + }()); + exports.Arg = Arg; + + }); + + unwrapExports(Utils); + var Utils_1 = Utils.Arg; + var Transports = createCommonjsModule(function (module, exports) { var __awaiter = (commonjsGlobal && commonjsGlobal.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { @@ -1446,19 +1554,33 @@ + var TransportType; (function (TransportType) { TransportType[TransportType["WebSockets"] = 0] = "WebSockets"; TransportType[TransportType["ServerSentEvents"] = 1] = "ServerSentEvents"; TransportType[TransportType["LongPolling"] = 2] = "LongPolling"; })(TransportType = exports.TransportType || (exports.TransportType = {})); + var TransferFormat; + (function (TransferFormat) { + TransferFormat[TransferFormat["Text"] = 1] = "Text"; + TransferFormat[TransferFormat["Binary"] = 2] = "Binary"; + })(TransferFormat = exports.TransferFormat || (exports.TransferFormat = {})); var WebSocketTransport = /** @class */ (function () { function WebSocketTransport(accessTokenFactory, logger) { this.logger = logger; this.accessTokenFactory = accessTokenFactory || (function () { return null; }); } - WebSocketTransport.prototype.connect = function (url, requestedTransferMode, connection) { + WebSocketTransport.prototype.connect = function (url, transferFormat, connection) { var _this = this; + Utils.Arg.isRequired(url, "url"); + Utils.Arg.isRequired(transferFormat, "transferFormat"); + Utils.Arg.isIn(transferFormat, TransferFormat, "transferFormat"); + Utils.Arg.isRequired(connection, "connection"); + if (typeof (WebSocket) === "undefined") { + throw new Error("'WebSocket' is not supported in your environment."); + } + this.logger.log(ILogger.LogLevel.Trace, "(WebSockets transport) Connecting"); return new Promise(function (resolve, reject) { url = url.replace(/^http/, "ws"); var token = _this.accessTokenFactory(); @@ -1466,19 +1588,19 @@ url += (url.indexOf("?") < 0 ? "?" : "&") + ("access_token=" + encodeURIComponent(token)); } var webSocket = new WebSocket(url); - if (requestedTransferMode == 2 /* Binary */) { + if (transferFormat === TransferFormat.Binary) { webSocket.binaryType = "arraybuffer"; } webSocket.onopen = function (event) { _this.logger.log(ILogger.LogLevel.Information, "WebSocket connected to " + url); _this.webSocket = webSocket; - resolve(requestedTransferMode); + resolve(); }; webSocket.onerror = function (event) { - reject(); + reject(event.error); }; webSocket.onmessage = function (message) { - _this.logger.log(ILogger.LogLevel.Trace, "(WebSockets transport) data received: " + message.data); + _this.logger.log(ILogger.LogLevel.Trace, "(WebSockets transport) data received. " + getDataDetail(message.data) + "."); if (_this.onreceive) { _this.onreceive(message.data); } @@ -1498,6 +1620,7 @@ }; WebSocketTransport.prototype.send = function (data) { if (this.webSocket && this.webSocket.readyState === WebSocket.OPEN) { + this.logger.log(ILogger.LogLevel.Trace, "(WebSockets transport) sending data. " + getDataDetail(data) + "."); this.webSocket.send(data); return Promise.resolve(); } @@ -1519,23 +1642,31 @@ this.accessTokenFactory = accessTokenFactory || (function () { return null; }); this.logger = logger; } - ServerSentEventsTransport.prototype.connect = function (url, requestedTransferMode, connection) { + ServerSentEventsTransport.prototype.connect = function (url, transferFormat, connection) { var _this = this; + Utils.Arg.isRequired(url, "url"); + Utils.Arg.isRequired(transferFormat, "transferFormat"); + Utils.Arg.isIn(transferFormat, TransferFormat, "transferFormat"); + Utils.Arg.isRequired(connection, "connection"); if (typeof (EventSource) === "undefined") { - Promise.reject("EventSource not supported by the browser."); + throw new Error("'EventSource' is not supported in your environment."); } + this.logger.log(ILogger.LogLevel.Trace, "(SSE transport) Connecting"); this.url = url; return new Promise(function (resolve, reject) { + if (transferFormat !== TransferFormat.Text) { + reject(new Error("The Server-Sent Events transport only supports the 'Text' transfer format")); + } var token = _this.accessTokenFactory(); if (token) { url += (url.indexOf("?") < 0 ? "?" : "&") + ("access_token=" + encodeURIComponent(token)); } - var eventSource = new EventSource(url); + var eventSource = new EventSource(url, { withCredentials: true }); try { eventSource.onmessage = function (e) { if (_this.onreceive) { try { - _this.logger.log(ILogger.LogLevel.Trace, "(SSE transport) data received: " + e.data); + _this.logger.log(ILogger.LogLevel.Trace, "(SSE transport) data received. " + getDataDetail(e.data) + "."); _this.onreceive(e.data); } catch (error) { @@ -1547,7 +1678,7 @@ } }; eventSource.onerror = function (e) { - reject(); + reject(new Error(e.message || "Error occurred")); // don't report an error if the transport did not start successfully if (_this.eventSource && _this.onclose) { _this.onclose(new Error(e.message || "Error occurred")); @@ -1557,7 +1688,7 @@ _this.logger.log(ILogger.LogLevel.Information, "SSE connected to " + _this.url); _this.eventSource = eventSource; // SSE is a text protocol - resolve(1 /* Text */); + resolve(); }; } catch (e) { @@ -1568,7 +1699,7 @@ ServerSentEventsTransport.prototype.send = function (data) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { - return [2 /*return*/, send(this.httpClient, this.url, this.accessTokenFactory, data)]; + return [2 /*return*/, send(this.logger, "SSE", this.httpClient, this.url, this.accessTokenFactory, data)]; }); }); }; @@ -1589,34 +1720,40 @@ this.logger = logger; this.pollAbort = new AbortController_1.AbortController(); } - LongPollingTransport.prototype.connect = function (url, requestedTransferMode, connection) { + LongPollingTransport.prototype.connect = function (url, transferFormat, connection) { + Utils.Arg.isRequired(url, "url"); + Utils.Arg.isRequired(transferFormat, "transferFormat"); + Utils.Arg.isIn(transferFormat, TransferFormat, "transferFormat"); + Utils.Arg.isRequired(connection, "connection"); this.url = url; + this.logger.log(ILogger.LogLevel.Trace, "(LongPolling transport) Connecting"); // Set a flag indicating we have inherent keep-alive in this transport. connection.features.inherentKeepAlive = true; - if (requestedTransferMode === 2 /* Binary */ && (typeof new XMLHttpRequest().responseType !== "string")) { + if (transferFormat === TransferFormat.Binary && (typeof new XMLHttpRequest().responseType !== "string")) { // This will work if we fix: https://github.com/aspnet/SignalR/issues/742 throw new Error("Binary protocols over XmlHttpRequest not implementing advanced features are not supported."); } - this.poll(this.url, requestedTransferMode); - return Promise.resolve(requestedTransferMode); + this.poll(this.url, transferFormat); + return Promise.resolve(); }; - LongPollingTransport.prototype.poll = function (url, transferMode) { + LongPollingTransport.prototype.poll = function (url, transferFormat) { return __awaiter(this, void 0, void 0, function () { var pollOptions, token, pollUrl, response, e_1; return __generator(this, function (_a) { switch (_a.label) { case 0: pollOptions = { - timeout: 120000, abortSignal: this.pollAbort.signal, - headers: new Map(), + headers: {}, + timeout: 90000, }; - if (transferMode === 2 /* Binary */) { + if (transferFormat === TransferFormat.Binary) { pollOptions.responseType = "arraybuffer"; } token = this.accessTokenFactory(); if (token) { - pollOptions.headers.set("Authorization", "Bearer " + token); + // tslint:disable-next-line:no-string-literal + pollOptions.headers["Authorization"] = "Bearer " + token; } _a.label = 1; case 1: @@ -1648,7 +1785,7 @@ else { // Process the response if (response.content) { - this.logger.log(ILogger.LogLevel.Trace, "(LongPolling transport) data received: " + response.content); + this.logger.log(ILogger.LogLevel.Trace, "(LongPolling transport) data received. " + getDataDetail(response.content) + "."); if (this.onreceive) { this.onreceive(response.content); } @@ -1682,7 +1819,7 @@ LongPollingTransport.prototype.send = function (data) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { - return [2 /*return*/, send(this.httpClient, this.url, this.accessTokenFactory, data)]; + return [2 /*return*/, send(this.logger, "LongPolling", this.httpClient, this.url, this.accessTokenFactory, data)]; }); }); }; @@ -1693,23 +1830,34 @@ return LongPollingTransport; }()); exports.LongPollingTransport = LongPollingTransport; - function send(httpClient, url, accessTokenFactory, content) { + function getDataDetail(data) { + var length = null; + if (data instanceof ArrayBuffer) { + length = "Binary data of length " + data.byteLength; + } + else if (typeof data === "string") { + length = "String data of length " + data.length; + } + return length; + } + function send(logger, transportName, httpClient, url, accessTokenFactory, content) { return __awaiter(this, void 0, void 0, function () { - var headers, token; - return __generator(this, function (_a) { - switch (_a.label) { + var headers, token, response, _a; + return __generator(this, function (_b) { + switch (_b.label) { case 0: token = accessTokenFactory(); if (token) { - headers = new Map(); - headers.set("Authorization", "Bearer " + accessTokenFactory()); + headers = (_a = {}, _a["Authorization"] = "Bearer " + accessTokenFactory(), _a); } + logger.log(ILogger.LogLevel.Trace, "(" + transportName + " transport) sending data. " + getDataDetail(content) + "."); return [4 /*yield*/, httpClient.post(url, { content: content, - headers: headers + headers: headers, })]; case 1: - _a.sent(); + response = _b.sent(); + logger.log(ILogger.LogLevel.Trace, "(" + transportName + " transport) request complete. Response status: " + response.statusCode + "."); return [2 /*return*/]; } }); @@ -1720,69 +1868,10 @@ unwrapExports(Transports); var Transports_1 = Transports.TransportType; - var Transports_2 = Transports.WebSocketTransport; - var Transports_3 = Transports.ServerSentEventsTransport; - var Transports_4 = Transports.LongPollingTransport; - - var Loggers = createCommonjsModule(function (module, exports) { - Object.defineProperty(exports, "__esModule", { value: true }); - - var NullLogger = /** @class */ (function () { - function NullLogger() { - } - NullLogger.prototype.log = function (logLevel, message) { - }; - return NullLogger; - }()); - exports.NullLogger = NullLogger; - var ConsoleLogger = /** @class */ (function () { - function ConsoleLogger(minimumLogLevel) { - this.minimumLogLevel = minimumLogLevel; - } - ConsoleLogger.prototype.log = function (logLevel, message) { - if (logLevel >= this.minimumLogLevel) { - switch (logLevel) { - case ILogger.LogLevel.Error: - console.error(ILogger.LogLevel[logLevel] + ": " + message); - break; - case ILogger.LogLevel.Warning: - console.warn(ILogger.LogLevel[logLevel] + ": " + message); - break; - case ILogger.LogLevel.Information: - console.info(ILogger.LogLevel[logLevel] + ": " + message); - break; - default: - console.log(ILogger.LogLevel[logLevel] + ": " + message); - break; - } - } - }; - return ConsoleLogger; - }()); - exports.ConsoleLogger = ConsoleLogger; - var LoggerFactory; - (function (LoggerFactory) { - function createLogger(logging) { - if (logging === undefined) { - return new ConsoleLogger(ILogger.LogLevel.Information); - } - if (logging === null) { - return new NullLogger(); - } - if (logging.log) { - return logging; - } - return new ConsoleLogger(logging); - } - LoggerFactory.createLogger = createLogger; - })(LoggerFactory = exports.LoggerFactory || (exports.LoggerFactory = {})); - - }); - - unwrapExports(Loggers); - var Loggers_1 = Loggers.NullLogger; - var Loggers_2 = Loggers.ConsoleLogger; - var Loggers_3 = Loggers.LoggerFactory; + var Transports_2 = Transports.TransferFormat; + var Transports_3 = Transports.WebSocketTransport; + var Transports_4 = Transports.ServerSentEventsTransport; + var Transports_5 = Transports.LongPollingTransport; var HttpConnection_1 = createCommonjsModule(function (module, exports) { var __awaiter = (commonjsGlobal && commonjsGlobal.__awaiter) || function (thisArg, _arguments, P, generator) { @@ -1825,131 +1914,231 @@ + var HttpConnection = /** @class */ (function () { function HttpConnection(url, options) { if (options === void 0) { options = {}; } this.features = {}; + Utils.Arg.isRequired(url, "url"); this.logger = Loggers.LoggerFactory.createLogger(options.logger); this.baseUrl = this.resolveUrl(url); options = options || {}; options.accessTokenFactory = options.accessTokenFactory || (function () { return null; }); - this.httpClient = options.httpClient || new HttpClient_1.DefaultHttpClient(); + this.httpClient = options.httpClient || new HttpClient_1.DefaultHttpClient(this.logger); this.connectionState = 2 /* Disconnected */; this.options = options; } - HttpConnection.prototype.start = function () { - return __awaiter(this, void 0, void 0, function () { - return __generator(this, function (_a) { - if (this.connectionState !== 2 /* Disconnected */) { - return [2 /*return*/, Promise.reject(new Error("Cannot start a connection that is not in the 'Disconnected' state."))]; - } - this.connectionState = 0 /* Connecting */; - this.startPromise = this.startInternal(); - return [2 /*return*/, this.startPromise]; - }); - }); + HttpConnection.prototype.start = function (transferFormat) { + Utils.Arg.isRequired(transferFormat, "transferFormat"); + Utils.Arg.isIn(transferFormat, Transports.TransferFormat, "transferFormat"); + this.logger.log(ILogger.LogLevel.Trace, "Starting connection with transfer format '" + Transports.TransferFormat[transferFormat] + "'."); + if (this.connectionState !== 2 /* Disconnected */) { + return Promise.reject(new Error("Cannot start a connection that is not in the 'Disconnected' state.")); + } + this.connectionState = 0 /* Connecting */; + this.startPromise = this.startInternal(transferFormat); + return this.startPromise; }; - HttpConnection.prototype.startInternal = function () { + HttpConnection.prototype.startInternal = function (transferFormat) { return __awaiter(this, void 0, void 0, function () { var _this = this; - var headers, token, negotiatePayload, negotiateResponse, requestedTransferMode, _a, e_1; + var token, headers, negotiateResponse, e_1, _a; return __generator(this, function (_b) { switch (_b.label) { case 0: - _b.trys.push([0, 5, , 6]); - if (!(this.options.transport === Transports.TransportType.WebSockets)) return [3 /*break*/, 1]; + _b.trys.push([0, 6, , 7]); + if (!(this.options.transport === Transports.TransportType.WebSockets)) return [3 /*break*/, 2]; // No need to add a connection ID in this case this.url = this.baseUrl; - this.transport = this.createTransport(this.options.transport, [Transports.TransportType[Transports.TransportType.WebSockets]]); - return [3 /*break*/, 3]; + this.transport = this.constructTransport(Transports.TransportType.WebSockets); + // We should just call connect directly in this case. + // No fallback or negotiate in this case. + return [4 /*yield*/, this.transport.connect(this.url, transferFormat, this)]; case 1: - headers = void 0; + // We should just call connect directly in this case. + // No fallback or negotiate in this case. + _b.sent(); + return [3 /*break*/, 5]; + case 2: token = this.options.accessTokenFactory(); + headers = void 0; if (token) { - headers = new Map(); - headers.set("Authorization", "Bearer " + token); + headers = (_a = {}, _a["Authorization"] = "Bearer " + token, _a); } - return [4 /*yield*/, this.httpClient.post(this.resolveNegotiateUrl(this.baseUrl), { - content: "", - headers: headers - })]; - case 2: - negotiatePayload = _b.sent(); - negotiateResponse = JSON.parse(negotiatePayload.content); - this.connectionId = negotiateResponse.connectionId; + return [4 /*yield*/, this.getNegotiationResponse(headers)]; + case 3: + negotiateResponse = _b.sent(); // the user tries to stop the the connection when it is being started - if (this.connectionState == 2 /* Disconnected */) { + if (this.connectionState === 2 /* Disconnected */) { return [2 /*return*/]; } - if (this.connectionId) { - this.url = this.baseUrl + (this.baseUrl.indexOf("?") === -1 ? "?" : "&") + ("id=" + this.connectionId); - this.transport = this.createTransport(this.options.transport, negotiateResponse.availableTransports); - } - _b.label = 3; - case 3: + return [4 /*yield*/, this.createTransport(this.options.transport, negotiateResponse, transferFormat, headers)]; + case 4: + _b.sent(); + _b.label = 5; + case 5: this.transport.onreceive = this.onreceive; this.transport.onclose = function (e) { return _this.stopConnection(true, e); }; - requestedTransferMode = this.features.transferMode === 2 /* Binary */ - ? 2 /* Binary */ - : 1 /* Text */; - _a = this.features; - return [4 /*yield*/, this.transport.connect(this.url, requestedTransferMode, this)]; - case 4: - _a.transferMode = _b.sent(); // only change the state if we were connecting to not overwrite // the state if the connection is already marked as Disconnected this.changeState(0 /* Connecting */, 1 /* Connected */); - return [3 /*break*/, 6]; - case 5: + return [3 /*break*/, 7]; + case 6: e_1 = _b.sent(); - this.logger.log(ILogger.LogLevel.Error, "Failed to start the connection. " + e_1); + this.logger.log(ILogger.LogLevel.Error, "Failed to start the connection: " + e_1); this.connectionState = 2 /* Disconnected */; this.transport = null; throw e_1; + case 7: return [2 /*return*/]; + } + }); + }); + }; + HttpConnection.prototype.getNegotiationResponse = function (headers) { + return __awaiter(this, void 0, void 0, function () { + var negotiateUrl, response, e_2; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + negotiateUrl = this.resolveNegotiateUrl(this.baseUrl); + this.logger.log(ILogger.LogLevel.Trace, "Sending negotiation request: " + negotiateUrl); + _a.label = 1; + case 1: + _a.trys.push([1, 3, , 4]); + return [4 /*yield*/, this.httpClient.post(negotiateUrl, { + content: "", + headers: headers, + })]; + case 2: + response = _a.sent(); + return [2 /*return*/, JSON.parse(response.content)]; + case 3: + e_2 = _a.sent(); + this.logger.log(ILogger.LogLevel.Error, "Failed to complete negotiation with the server: " + e_2); + throw e_2; + case 4: return [2 /*return*/]; + } + }); + }); + }; + HttpConnection.prototype.updateConnectionId = function (negotiateResponse) { + this.connectionId = negotiateResponse.connectionId; + this.url = this.baseUrl + (this.baseUrl.indexOf("?") === -1 ? "?" : "&") + ("id=" + this.connectionId); + }; + HttpConnection.prototype.createTransport = function (requestedTransport, negotiateResponse, requestedTransferFormat, headers) { + return __awaiter(this, void 0, void 0, function () { + var transports, _i, transports_1, endpoint, transport, ex_1; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + this.updateConnectionId(negotiateResponse); + if (!this.isITransport(requestedTransport)) return [3 /*break*/, 2]; + this.logger.log(ILogger.LogLevel.Trace, "Connection was provided an instance of ITransport, using that directly."); + this.transport = requestedTransport; + return [4 /*yield*/, this.transport.connect(this.url, requestedTransferFormat, this)]; + case 1: + _a.sent(); + // only change the state if we were connecting to not overwrite + // the state if the connection is already marked as Disconnected + this.changeState(0 /* Connecting */, 1 /* Connected */); + return [2 /*return*/]; + case 2: + transports = negotiateResponse.availableTransports; + _i = 0, transports_1 = transports; + _a.label = 3; + case 3: + if (!(_i < transports_1.length)) return [3 /*break*/, 9]; + endpoint = transports_1[_i]; + this.connectionState = 0 /* Connecting */; + transport = this.resolveTransport(endpoint, requestedTransport, requestedTransferFormat); + if (!(typeof transport === "number")) return [3 /*break*/, 8]; + this.transport = this.constructTransport(transport); + if (!(negotiateResponse.connectionId === null)) return [3 /*break*/, 5]; + return [4 /*yield*/, this.getNegotiationResponse(headers)]; + case 4: + negotiateResponse = _a.sent(); + this.updateConnectionId(negotiateResponse); + _a.label = 5; + case 5: + _a.trys.push([5, 7, , 8]); + return [4 /*yield*/, this.transport.connect(this.url, requestedTransferFormat, this)]; case 6: - ; + _a.sent(); + this.changeState(0 /* Connecting */, 1 /* Connected */); return [2 /*return*/]; + case 7: + ex_1 = _a.sent(); + this.logger.log(ILogger.LogLevel.Error, "Failed to start the transport '" + Transports.TransportType[transport] + "': " + ex_1); + this.connectionState = 2 /* Disconnected */; + negotiateResponse.connectionId = null; + return [3 /*break*/, 8]; + case 8: + _i++; + return [3 /*break*/, 3]; + case 9: throw new Error("Unable to initialize any of the available transports."); } }); }); }; - HttpConnection.prototype.createTransport = function (transport, availableTransports) { - if ((transport === null || transport === undefined) && availableTransports.length > 0) { - transport = Transports.TransportType[availableTransports[0]]; + HttpConnection.prototype.constructTransport = function (transport) { + switch (transport) { + case Transports.TransportType.WebSockets: + return new Transports.WebSocketTransport(this.options.accessTokenFactory, this.logger); + case Transports.TransportType.ServerSentEvents: + return new Transports.ServerSentEventsTransport(this.httpClient, this.options.accessTokenFactory, this.logger); + case Transports.TransportType.LongPolling: + return new Transports.LongPollingTransport(this.httpClient, this.options.accessTokenFactory, this.logger); + default: + throw new Error("Unknown transport: " + transport + "."); } - if (transport === Transports.TransportType.WebSockets && availableTransports.indexOf(Transports.TransportType[transport]) >= 0) { - return new Transports.WebSocketTransport(this.options.accessTokenFactory, this.logger); - } - if (transport === Transports.TransportType.ServerSentEvents && availableTransports.indexOf(Transports.TransportType[transport]) >= 0) { - return new Transports.ServerSentEventsTransport(this.httpClient, this.options.accessTokenFactory, this.logger); - } - if (transport === Transports.TransportType.LongPolling && availableTransports.indexOf(Transports.TransportType[transport]) >= 0) { - return new Transports.LongPollingTransport(this.httpClient, this.options.accessTokenFactory, this.logger); + }; + HttpConnection.prototype.resolveTransport = function (endpoint, requestedTransport, requestedTransferFormat) { + var transport = Transports.TransportType[endpoint.transport]; + if (transport === null || transport === undefined) { + this.logger.log(ILogger.LogLevel.Trace, "Skipping transport '" + endpoint.transport + "' because it is not supported by this client."); } - if (this.isITransport(transport)) { - return transport; + else { + var transferFormats = endpoint.transferFormats.map(function (s) { return Transports.TransferFormat[s]; }); + if (!requestedTransport || transport === requestedTransport) { + if (transferFormats.indexOf(requestedTransferFormat) >= 0) { + if ((transport === Transports.TransportType.WebSockets && typeof WebSocket === "undefined") || + (transport === Transports.TransportType.ServerSentEvents && typeof EventSource === "undefined")) { + this.logger.log(ILogger.LogLevel.Trace, "Skipping transport '" + Transports.TransportType[transport] + "' because it is not supported in your environment.'"); + } + else { + this.logger.log(ILogger.LogLevel.Trace, "Selecting transport '" + Transports.TransportType[transport] + "'"); + return transport; + } + } + else { + this.logger.log(ILogger.LogLevel.Trace, "Skipping transport '" + Transports.TransportType[transport] + "' because it does not support the requested transfer format '" + Transports.TransferFormat[requestedTransferFormat] + "'."); + } + } + else { + this.logger.log(ILogger.LogLevel.Trace, "Skipping transport '" + Transports.TransportType[transport] + "' because it was disabled by the client."); + } } - throw new Error("No available transports found."); + return null; }; HttpConnection.prototype.isITransport = function (transport) { return typeof (transport) === "object" && "connect" in transport; }; HttpConnection.prototype.changeState = function (from, to) { - if (this.connectionState == from) { + if (this.connectionState === from) { this.connectionState = to; return true; } return false; }; HttpConnection.prototype.send = function (data) { - if (this.connectionState != 1 /* Connected */) { - throw new Error("Cannot send data if the connection is not in the 'Connected' State"); + if (this.connectionState !== 1 /* Connected */) { + throw new Error("Cannot send data if the connection is not in the 'Connected' State."); } return this.transport.send(data); }; HttpConnection.prototype.stop = function (error) { return __awaiter(this, void 0, void 0, function () { - var previousState, e_2; + var previousState, e_3; return __generator(this, function (_a) { switch (_a.label) { case 0: @@ -1963,10 +2152,10 @@ _a.sent(); return [3 /*break*/, 4]; case 3: - e_2 = _a.sent(); + e_3 = _a.sent(); return [3 /*break*/, 4]; case 4: - this.stopConnection(/*raiseClosed*/ previousState == 1 /* Connected */, error); + this.stopConnection(/*raiseClosed*/ previousState === 1 /* Connected */, error); return [2 /*return*/]; } }); @@ -1993,7 +2182,7 @@ if (url.lastIndexOf("https://", 0) === 0 || url.lastIndexOf("http://", 0) === 0) { return url; } - if (typeof window === 'undefined' || !window || !window.document) { + if (typeof window === "undefined" || !window || !window.document) { throw new Error("Cannot resolve '" + url + "'."); } var parser = window.document.createElement("a"); @@ -2001,11 +2190,11 @@ var baseUrl = (!parser.protocol || parser.protocol === ":") ? window.document.location.protocol + "//" + (parser.host || window.document.location.host) : parser.protocol + "//" + parser.host; - if (!url || url[0] != '/') { - url = '/' + url; + if (!url || url[0] !== "/") { + url = "/" + url; } var normalizedUrl = baseUrl + url; - this.logger.log(ILogger.LogLevel.Information, "Normalizing '" + url + "' to '" + normalizedUrl + "'"); + this.logger.log(ILogger.LogLevel.Information, "Normalizing '" + url + "' to '" + normalizedUrl + "'."); return normalizedUrl; }; HttpConnection.prototype.resolveNegotiateUrl = function (url) { @@ -2027,66 +2216,6 @@ unwrapExports(HttpConnection_1); var HttpConnection_2 = HttpConnection_1.HttpConnection; - var Observable = createCommonjsModule(function (module, exports) { - Object.defineProperty(exports, "__esModule", { value: true }); - var Subscription = /** @class */ (function () { - function Subscription(subject, observer) { - this.subject = subject; - this.observer = observer; - } - Subscription.prototype.dispose = function () { - var index = this.subject.observers.indexOf(this.observer); - if (index > -1) { - this.subject.observers.splice(index, 1); - } - if (this.subject.observers.length === 0) { - this.subject.cancelCallback().catch(function (_) { }); - } - }; - return Subscription; - }()); - exports.Subscription = Subscription; - var Subject = /** @class */ (function () { - function Subject(cancelCallback) { - this.observers = []; - this.cancelCallback = cancelCallback; - } - Subject.prototype.next = function (item) { - for (var _i = 0, _a = this.observers; _i < _a.length; _i++) { - var observer = _a[_i]; - observer.next(item); - } - }; - Subject.prototype.error = function (err) { - for (var _i = 0, _a = this.observers; _i < _a.length; _i++) { - var observer = _a[_i]; - if (observer.error) { - observer.error(err); - } - } - }; - Subject.prototype.complete = function () { - for (var _i = 0, _a = this.observers; _i < _a.length; _i++) { - var observer = _a[_i]; - if (observer.complete) { - observer.complete(); - } - } - }; - Subject.prototype.subscribe = function (observer) { - this.observers.push(observer); - return new Subscription(this, observer); - }; - return Subject; - }()); - exports.Subject = Subject; - - }); - - unwrapExports(Observable); - var Observable_1 = Observable.Subscription; - var Observable_2 = Observable.Subject; - var TextMessageFormat_1 = createCommonjsModule(function (module, exports) { Object.defineProperty(exports, "__esModule", { value: true }); var TextMessageFormat = /** @class */ (function () { @@ -2096,14 +2225,15 @@ return "" + output + TextMessageFormat.RecordSeparator; }; TextMessageFormat.parse = function (input) { - if (input[input.length - 1] != TextMessageFormat.RecordSeparator) { + if (input[input.length - 1] !== TextMessageFormat.RecordSeparator) { throw new Error("Message is incomplete."); } var messages = input.split(TextMessageFormat.RecordSeparator); messages.pop(); return messages; }; - TextMessageFormat.RecordSeparator = String.fromCharCode(0x1e); + TextMessageFormat.RecordSeparatorCode = 0x1e; + TextMessageFormat.RecordSeparator = String.fromCharCode(TextMessageFormat.RecordSeparatorCode); return TextMessageFormat; }()); exports.TextMessageFormat = TextMessageFormat; @@ -2116,27 +2246,86 @@ var JsonHubProtocol_1 = createCommonjsModule(function (module, exports) { Object.defineProperty(exports, "__esModule", { value: true }); + + + exports.JSON_HUB_PROTOCOL_NAME = "json"; var JsonHubProtocol = /** @class */ (function () { function JsonHubProtocol() { this.name = exports.JSON_HUB_PROTOCOL_NAME; - this.type = 1 /* Text */; + this.version = 1; + this.transferFormat = Transports.TransferFormat.Text; } - JsonHubProtocol.prototype.parseMessages = function (input) { + JsonHubProtocol.prototype.parseMessages = function (input, logger) { if (!input) { return []; } + if (logger === null) { + logger = new Loggers.NullLogger(); + } // Parse the messages var messages = TextMessageFormat_1.TextMessageFormat.parse(input); var hubMessages = []; - for (var i = 0; i < messages.length; ++i) { - hubMessages.push(JSON.parse(messages[i])); + for (var _i = 0, messages_1 = messages; _i < messages_1.length; _i++) { + var message = messages_1[_i]; + var parsedMessage = JSON.parse(message); + if (typeof parsedMessage.type !== "number") { + throw new Error("Invalid payload."); + } + switch (parsedMessage.type) { + case 1 /* Invocation */: + this.isInvocationMessage(parsedMessage); + break; + case 2 /* StreamItem */: + this.isStreamItemMessage(parsedMessage); + break; + case 3 /* Completion */: + this.isCompletionMessage(parsedMessage); + break; + case 6 /* Ping */: + // Single value, no need to validate + break; + case 7 /* Close */: + // All optional values, no need to validate + break; + default: + // Future protocol changes can add message types, old clients can ignore them + logger.log(ILogger.LogLevel.Information, "Unknown message type '" + parsedMessage.type + "' ignored."); + continue; + } + hubMessages.push(parsedMessage); } return hubMessages; }; JsonHubProtocol.prototype.writeMessage = function (message) { return TextMessageFormat_1.TextMessageFormat.write(JSON.stringify(message)); }; + JsonHubProtocol.prototype.isInvocationMessage = function (message) { + this.assertNotEmptyString(message.target, "Invalid payload for Invocation message."); + if (message.invocationId !== undefined) { + this.assertNotEmptyString(message.invocationId, "Invalid payload for Invocation message."); + } + }; + JsonHubProtocol.prototype.isStreamItemMessage = function (message) { + this.assertNotEmptyString(message.invocationId, "Invalid payload for StreamItem message."); + if (message.item === undefined) { + throw new Error("Invalid payload for StreamItem message."); + } + }; + JsonHubProtocol.prototype.isCompletionMessage = function (message) { + if (message.result && message.error) { + throw new Error("Invalid payload for Completion message."); + } + if (!message.result && message.error) { + this.assertNotEmptyString(message.error, "Invalid payload for Completion message."); + } + this.assertNotEmptyString(message.invocationId, "Invalid payload for Completion message."); + }; + JsonHubProtocol.prototype.assertNotEmptyString = function (value, errorMessage) { + if (typeof value !== "string" || value === "") { + throw new Error(errorMessage); + } + }; return JsonHubProtocol; }()); exports.JsonHubProtocol = JsonHubProtocol; @@ -2147,58 +2336,65 @@ var JsonHubProtocol_2 = JsonHubProtocol_1.JSON_HUB_PROTOCOL_NAME; var JsonHubProtocol_3 = JsonHubProtocol_1.JsonHubProtocol; - var Base64EncodedHubProtocol_1 = createCommonjsModule(function (module, exports) { + var Observable = createCommonjsModule(function (module, exports) { Object.defineProperty(exports, "__esModule", { value: true }); - var Base64EncodedHubProtocol = /** @class */ (function () { - function Base64EncodedHubProtocol(protocol) { - this.wrappedProtocol = protocol; - this.name = this.wrappedProtocol.name; - this.type = 1 /* Text */; - } - Base64EncodedHubProtocol.prototype.parseMessages = function (input) { - // The format of the message is `size:message;` - var pos = input.indexOf(":"); - if (pos == -1 || input[input.length - 1] != ';') { - throw new Error("Invalid payload."); - } - var lenStr = input.substring(0, pos); - if (!/^[0-9]+$/.test(lenStr)) { - throw new Error("Invalid length: '" + lenStr + "'"); - } - var messageSize = parseInt(lenStr, 10); - // 2 accounts for ':' after message size and trailing ';' - if (messageSize != input.length - pos - 2) { - throw new Error("Invalid message size."); - } - var encodedMessage = input.substring(pos + 1, input.length - 1); - // atob/btoa are browsers APIs but they can be polyfilled. If this becomes problematic we can use - // base64-js module - var s = atob(encodedMessage); - var payload = new Uint8Array(s.length); - for (var i = 0; i < payload.length; i++) { - payload[i] = s.charCodeAt(i); - } - return this.wrappedProtocol.parseMessages(payload.buffer); - }; - Base64EncodedHubProtocol.prototype.writeMessage = function (message) { - var payload = new Uint8Array(this.wrappedProtocol.writeMessage(message)); - var s = ""; - for (var i = 0; i < payload.byteLength; i++) { - s += String.fromCharCode(payload[i]); - } - // atob/btoa are browsers APIs but they can be polyfilled. If this becomes problematic we can use - // base64-js module - var encodedMessage = btoa(s); - return encodedMessage.length.toString() + ":" + encodedMessage + ";"; - }; - return Base64EncodedHubProtocol; + var Subscription = /** @class */ (function () { + function Subscription(subject, observer) { + this.subject = subject; + this.observer = observer; + } + Subscription.prototype.dispose = function () { + var index = this.subject.observers.indexOf(this.observer); + if (index > -1) { + this.subject.observers.splice(index, 1); + } + if (this.subject.observers.length === 0) { + this.subject.cancelCallback().catch(function (_) { }); + } + }; + return Subscription; + }()); + exports.Subscription = Subscription; + var Subject = /** @class */ (function () { + function Subject(cancelCallback) { + this.observers = []; + this.cancelCallback = cancelCallback; + } + Subject.prototype.next = function (item) { + for (var _i = 0, _a = this.observers; _i < _a.length; _i++) { + var observer = _a[_i]; + observer.next(item); + } + }; + Subject.prototype.error = function (err) { + for (var _i = 0, _a = this.observers; _i < _a.length; _i++) { + var observer = _a[_i]; + if (observer.error) { + observer.error(err); + } + } + }; + Subject.prototype.complete = function () { + for (var _i = 0, _a = this.observers; _i < _a.length; _i++) { + var observer = _a[_i]; + if (observer.complete) { + observer.complete(); + } + } + }; + Subject.prototype.subscribe = function (observer) { + this.observers.push(observer); + return new Subscription(this, observer); + }; + return Subject; }()); - exports.Base64EncodedHubProtocol = Base64EncodedHubProtocol; + exports.Subject = Subject; }); - unwrapExports(Base64EncodedHubProtocol_1); - var Base64EncodedHubProtocol_2 = Base64EncodedHubProtocol_1.Base64EncodedHubProtocol; + unwrapExports(Observable); + var Observable_1 = Observable.Subscription; + var Observable_2 = Observable.Subject; var HubConnection_1 = createCommonjsModule(function (module, exports) { var __awaiter = (commonjsGlobal && commonjsGlobal.__awaiter) || function (thisArg, _arguments, P, generator) { @@ -2244,7 +2440,6 @@ - var DEFAULT_TIMEOUT_IN_MS = 30 * 1000; var HubConnection = /** @class */ (function () { function HubConnection(urlOrConnection, options) { @@ -2252,6 +2447,7 @@ var _this = this; options = options || {}; this.timeoutInMilliseconds = options.timeoutInMilliseconds || DEFAULT_TIMEOUT_IN_MS; + this.protocol = options.protocol || new JsonHubProtocol_1.JsonHubProtocol(); if (typeof urlOrConnection === "string") { this.connection = new HttpConnection_1.HttpConnection(urlOrConnection, options); } @@ -2259,46 +2455,107 @@ this.connection = urlOrConnection; } this.logger = Loggers.LoggerFactory.createLogger(options.logger); - this.protocol = options.protocol || new JsonHubProtocol_1.JsonHubProtocol(); this.connection.onreceive = function (data) { return _this.processIncomingData(data); }; this.connection.onclose = function (error) { return _this.connectionClosed(error); }; - this.callbacks = new Map(); - this.methods = new Map(); + this.callbacks = {}; + this.methods = {}; this.closedCallbacks = []; this.id = 0; } HubConnection.prototype.processIncomingData = function (data) { - if (this.timeoutHandle !== undefined) { - clearTimeout(this.timeoutHandle); - } - // Parse the messages - var messages = this.protocol.parseMessages(data); - for (var i = 0; i < messages.length; ++i) { - var message = messages[i]; - switch (message.type) { - case 1 /* Invocation */: - this.invokeClientMethod(message); - break; - case 2 /* StreamItem */: - case 3 /* Completion */: - var callback = this.callbacks.get(message.invocationId); - if (callback != null) { - if (message.type === 3 /* Completion */) { - this.callbacks.delete(message.invocationId); + this.cleanupTimeout(); + if (!this.receivedHandshakeResponse) { + data = this.processHandshakeResponse(data); + this.receivedHandshakeResponse = true; + } + // Data may have all been read when processing handshake response + if (data) { + // Parse the messages + var messages = this.protocol.parseMessages(data, this.logger); + for (var _i = 0, messages_1 = messages; _i < messages_1.length; _i++) { + var message = messages_1[_i]; + switch (message.type) { + case 1 /* Invocation */: + this.invokeClientMethod(message); + break; + case 2 /* StreamItem */: + case 3 /* Completion */: + var callback = this.callbacks[message.invocationId]; + if (callback != null) { + if (message.type === 3 /* Completion */) { + delete this.callbacks[message.invocationId]; + } + callback(message); } - callback(message); - } - break; - case 6 /* Ping */: - // Don't care about pings - break; - default: - this.logger.log(ILogger.LogLevel.Warning, "Invalid message type: " + data); - break; + break; + case 6 /* Ping */: + // Don't care about pings + break; + case 7 /* Close */: + this.logger.log(ILogger.LogLevel.Information, "Close message received from server."); + this.connection.stop(message.error ? new Error("Server returned an error on close: " + message.error) : null); + break; + default: + this.logger.log(ILogger.LogLevel.Warning, "Invalid message type: " + message.type); + break; + } } } this.configureTimeout(); }; + HubConnection.prototype.processHandshakeResponse = function (data) { + var responseMessage; + var messageData; + var remainingData; + try { + if (data instanceof ArrayBuffer) { + // Format is binary but still need to read JSON text from handshake response + var binaryData = new Uint8Array(data); + var separatorIndex = binaryData.indexOf(TextMessageFormat_1.TextMessageFormat.RecordSeparatorCode); + if (separatorIndex === -1) { + throw new Error("Message is incomplete."); + } + // content before separator is handshake response + // optional content after is additional messages + var responseLength = separatorIndex + 1; + messageData = String.fromCharCode.apply(null, binaryData.slice(0, responseLength)); + remainingData = (binaryData.byteLength > responseLength) ? binaryData.slice(responseLength).buffer : null; + } + else { + var textData = data; + var separatorIndex = textData.indexOf(TextMessageFormat_1.TextMessageFormat.RecordSeparator); + if (separatorIndex === -1) { + throw new Error("Message is incomplete."); + } + // content before separator is handshake response + // optional content after is additional messages + var responseLength = separatorIndex + 1; + messageData = textData.substring(0, responseLength); + remainingData = (textData.length > responseLength) ? textData.substring(responseLength) : null; + } + // At this point we should have just the single handshake message + var messages = TextMessageFormat_1.TextMessageFormat.parse(messageData); + responseMessage = JSON.parse(messages[0]); + } + catch (e) { + var message = "Error parsing handshake response: " + e; + this.logger.log(ILogger.LogLevel.Error, message); + var error = new Error(message); + this.connection.stop(error); + throw error; + } + if (responseMessage.error) { + var message = "Server returned handshake error: " + responseMessage.error; + this.logger.log(ILogger.LogLevel.Error, message); + this.connection.stop(new Error(message)); + } + else { + this.logger.log(ILogger.LogLevel.Trace, "Server handshake complete."); + } + // multiple messages could have arrived with handshake + // return additional data to be parsed as usual, or null if all parsed + return remainingData; + }; HubConnection.prototype.configureTimeout = function () { var _this = this; if (!this.connection.features || !this.connection.features.inherentKeepAlive) { @@ -2313,7 +2570,7 @@ }; HubConnection.prototype.invokeClientMethod = function (invocationMessage) { var _this = this; - var methods = this.methods.get(invocationMessage.target.toLowerCase()); + var methods = this.methods[invocationMessage.target.toLowerCase()]; if (methods) { methods.forEach(function (m) { return m.apply(_this, invocationMessage.arguments); }); if (invocationMessage.invocationId) { @@ -2329,34 +2586,35 @@ }; HubConnection.prototype.connectionClosed = function (error) { var _this = this; - this.callbacks.forEach(function (callback) { - callback(undefined, error ? error : new Error("Invocation canceled due to connection being closed.")); - }); - this.callbacks.clear(); - this.closedCallbacks.forEach(function (c) { return c.apply(_this, [error]); }); + var callbacks = this.callbacks; + this.callbacks = {}; + Object.keys(callbacks) + .forEach(function (key) { + var callback = callbacks[key]; + callback(undefined, error ? error : new Error("Invocation canceled due to connection being closed.")); + }); this.cleanupTimeout(); + this.closedCallbacks.forEach(function (c) { return c.apply(_this, [error]); }); }; HubConnection.prototype.start = function () { return __awaiter(this, void 0, void 0, function () { - var requestedTransferMode, actualTransferMode; return __generator(this, function (_a) { switch (_a.label) { case 0: - requestedTransferMode = (this.protocol.type === 2 /* Binary */) - ? 2 /* Binary */ - : 1 /* Text */; - this.connection.features.transferMode = requestedTransferMode; - return [4 /*yield*/, this.connection.start()]; + this.logger.log(ILogger.LogLevel.Trace, "Starting HubConnection."); + this.receivedHandshakeResponse = false; + return [4 /*yield*/, this.connection.start(this.protocol.transferFormat)]; case 1: _a.sent(); - actualTransferMode = this.connection.features.transferMode; - return [4 /*yield*/, this.connection.send(TextMessageFormat_1.TextMessageFormat.write(JSON.stringify({ protocol: this.protocol.name })))]; + this.logger.log(ILogger.LogLevel.Trace, "Sending handshake request."); + // Handshake request is always JSON + return [4 /*yield*/, this.connection.send(TextMessageFormat_1.TextMessageFormat.write(JSON.stringify({ protocol: this.protocol.name, version: this.protocol.version })))]; case 2: + // Handshake request is always JSON _a.sent(); this.logger.log(ILogger.LogLevel.Information, "Using HubProtocol '" + this.protocol.name + "'."); - if (requestedTransferMode === 2 /* Binary */ && actualTransferMode === 1 /* Text */) { - this.protocol = new Base64EncodedHubProtocol_1.Base64EncodedHubProtocol(this.protocol); - } + // defensively cleanup timeout in case we receive a message from the server before we finish start + this.cleanupTimeout(); this.configureTimeout(); return [2 /*return*/]; } @@ -2364,6 +2622,7 @@ }); }; HubConnection.prototype.stop = function () { + this.logger.log(ILogger.LogLevel.Trace, "Stopping HubConnection."); this.cleanupTimeout(); return this.connection.stop(); }; @@ -2376,33 +2635,32 @@ var invocationDescriptor = this.createStreamInvocation(methodName, args); var subject = new Observable.Subject(function () { var cancelInvocation = _this.createCancelInvocation(invocationDescriptor.invocationId); - var message = _this.protocol.writeMessage(cancelInvocation); - _this.callbacks.delete(invocationDescriptor.invocationId); - return _this.connection.send(message); + var cancelMessage = _this.protocol.writeMessage(cancelInvocation); + delete _this.callbacks[invocationDescriptor.invocationId]; + return _this.connection.send(cancelMessage); }); - this.callbacks.set(invocationDescriptor.invocationId, function (invocationEvent, error) { + this.callbacks[invocationDescriptor.invocationId] = function (invocationEvent, error) { if (error) { subject.error(error); return; } if (invocationEvent.type === 3 /* Completion */) { - var completionMessage = invocationEvent; - if (completionMessage.error) { - subject.error(new Error(completionMessage.error)); + if (invocationEvent.error) { + subject.error(new Error(invocationEvent.error)); } else { subject.complete(); } } else { - subject.next(invocationEvent.item); + subject.next((invocationEvent.item)); } - }); + }; var message = this.protocol.writeMessage(invocationDescriptor); this.connection.send(message) .catch(function (e) { subject.error(e); - _this.callbacks.delete(invocationDescriptor.invocationId); + delete _this.callbacks[invocationDescriptor.invocationId]; }); return subject; }; @@ -2423,7 +2681,7 @@ } var invocationDescriptor = this.createInvocation(methodName, args, false); var p = new Promise(function (resolve, reject) { - _this.callbacks.set(invocationDescriptor.invocationId, function (invocationEvent, error) { + _this.callbacks[invocationDescriptor.invocationId] = function (invocationEvent, error) { if (error) { reject(error); return; @@ -2440,38 +2698,50 @@ else { reject(new Error("Unexpected message type: " + invocationEvent.type)); } - }); + }; var message = _this.protocol.writeMessage(invocationDescriptor); _this.connection.send(message) .catch(function (e) { reject(e); - _this.callbacks.delete(invocationDescriptor.invocationId); + delete _this.callbacks[invocationDescriptor.invocationId]; }); }); return p; }; - HubConnection.prototype.on = function (methodName, method) { - if (!methodName || !method) { + HubConnection.prototype.on = function (methodName, newMethod) { + if (!methodName || !newMethod) { return; } methodName = methodName.toLowerCase(); - if (!this.methods.has(methodName)) { - this.methods.set(methodName, []); + if (!this.methods[methodName]) { + this.methods[methodName] = []; } - this.methods.get(methodName).push(method); + // Preventing adding the same handler multiple times. + if (this.methods[methodName].indexOf(newMethod) !== -1) { + return; + } + this.methods[methodName].push(newMethod); }; HubConnection.prototype.off = function (methodName, method) { - if (!methodName || !method) { + if (!methodName) { return; } methodName = methodName.toLowerCase(); - var handlers = this.methods.get(methodName); + var handlers = this.methods[methodName]; if (!handlers) { return; } - var removeIdx = handlers.indexOf(method); - if (removeIdx != -1) { - handlers.splice(removeIdx, 1); + if (method) { + var removeIdx = handlers.indexOf(method); + if (removeIdx !== -1) { + handlers.splice(removeIdx, 1); + if (handlers.length === 0) { + delete this.methods[methodName]; + } + } + } + else { + delete this.methods[methodName]; } }; HubConnection.prototype.onclose = function (callback) { @@ -2487,19 +2757,19 @@ HubConnection.prototype.createInvocation = function (methodName, args, nonblocking) { if (nonblocking) { return { - type: 1 /* Invocation */, - target: methodName, arguments: args, + target: methodName, + type: 1 /* Invocation */, }; } else { var id = this.id; this.id++; return { - type: 1 /* Invocation */, + arguments: args, invocationId: id.toString(), target: methodName, - arguments: args, + type: 1 /* Invocation */, }; } }; @@ -2507,16 +2777,16 @@ var id = this.id; this.id++; return { - type: 4 /* StreamInvocation */, + arguments: args, invocationId: id.toString(), target: methodName, - arguments: args, + type: 4 /* StreamInvocation */, }; }; HubConnection.prototype.createCancelInvocation = function (id) { return { - type: 5 /* CancelInvocation */, invocationId: id, + type: 5 /* CancelInvocation */, }; }; return HubConnection;