From bb79a9760c7052622bbc5aaeabb9fe7a600b9b84 Mon Sep 17 00:00:00 2001 From: Pawel Kadluczka Date: Mon, 11 Sep 2017 13:37:53 -0700 Subject: [PATCH 1/4] Enabling creating HubConnection without HttpConnection --- .../HubConnection.ts | 11 +++++++++-- .../IHubConnectionOptions.ts | 4 ++-- .../wwwroot/js/hubConnectionTests.js | 12 ++++++------ 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/client-ts/Microsoft.AspNetCore.SignalR.Client.TS/HubConnection.ts b/client-ts/Microsoft.AspNetCore.SignalR.Client.TS/HubConnection.ts index 8c36667fc3..fa6f8f93fa 100644 --- a/client-ts/Microsoft.AspNetCore.SignalR.Client.TS/HubConnection.ts +++ b/client-ts/Microsoft.AspNetCore.SignalR.Client.TS/HubConnection.ts @@ -3,6 +3,7 @@ import { ConnectionClosed } from "./Common" import { IConnection } from "./IConnection" +import { HttpConnection} from "./HttpConnection" import { TransportType, TransferMode } from "./Transports" import { Subject, Observable } from "./Observable" import { IHubProtocol, ProtocolType, MessageType, HubMessage, CompletionMessage, ResultMessage, InvocationMessage, NegotiationMessage } from "./IHubProtocol"; @@ -28,9 +29,15 @@ export class HubConnection { private id: number; private connectionClosedCallback: ConnectionClosed; - constructor(connection: IConnection, options: IHubConnectionOptions = {}) { - this.connection = connection; + constructor(urlOrConnection: string | IConnection, options: IHubConnectionOptions = {}) { options = options || {}; + if (typeof urlOrConnection === "string") { + this.connection = new HttpConnection(urlOrConnection, options); + } + else { + this.connection = urlOrConnection; + } + this.logger = LoggerFactory.createLogger(options.logging); this.protocol = options.protocol || new JsonHubProtocol(); diff --git a/client-ts/Microsoft.AspNetCore.SignalR.Client.TS/IHubConnectionOptions.ts b/client-ts/Microsoft.AspNetCore.SignalR.Client.TS/IHubConnectionOptions.ts index 7e41c9649e..9aa80a1cfe 100644 --- a/client-ts/Microsoft.AspNetCore.SignalR.Client.TS/IHubConnectionOptions.ts +++ b/client-ts/Microsoft.AspNetCore.SignalR.Client.TS/IHubConnectionOptions.ts @@ -1,10 +1,10 @@ // 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. +import { IHttpConnectionOptions } from "./IHttpConnectionOptions" import { IHubProtocol } from "./IHubProtocol" import { ILogger, LogLevel } from "./ILogger" -export interface IHubConnectionOptions { +export interface IHubConnectionOptions extends IHttpConnectionOptions { protocol?: IHubProtocol; - logging?: ILogger | LogLevel; } diff --git a/client-ts/Microsoft.AspNetCore.SignalR.Test.Server/wwwroot/js/hubConnectionTests.js b/client-ts/Microsoft.AspNetCore.SignalR.Test.Server/wwwroot/js/hubConnectionTests.js index f58435025a..c53ba3d710 100644 --- a/client-ts/Microsoft.AspNetCore.SignalR.Test.Server/wwwroot/js/hubConnectionTests.js +++ b/client-ts/Microsoft.AspNetCore.SignalR.Test.Server/wwwroot/js/hubConnectionTests.js @@ -17,7 +17,7 @@ describe('hubConnection', function () { protocol: protocol, logging: signalR.LogLevel.Trace }; - var hubConnection = new signalR.HubConnection(new signalR.HttpConnection(TESTHUBENDPOINT_URL, options), options); + var hubConnection = new signalR.HubConnection(TESTHUBENDPOINT_URL, options); hubConnection.onClosed = function (error) { expect(error).toBe(undefined); done(); @@ -44,7 +44,7 @@ describe('hubConnection', function () { protocol: protocol, logging: signalR.LogLevel.Trace }; - var hubConnection = new signalR.HubConnection(new signalR.HttpConnection(TESTHUBENDPOINT_URL, options), options); + var hubConnection = new signalR.HubConnection(TESTHUBENDPOINT_URL, options); hubConnection.onClosed = function (error) { expect(error).toBe(undefined); @@ -79,7 +79,7 @@ describe('hubConnection', function () { protocol: protocol, logging: signalR.LogLevel.Trace }; - var hubConnection = new signalR.HubConnection(new signalR.HttpConnection(TESTHUBENDPOINT_URL, options), options); + var hubConnection = new signalR.HubConnection(TESTHUBENDPOINT_URL, options); hubConnection.start().then(function () { hubConnection.invoke('ThrowException', errorMessage).then(function () { @@ -105,7 +105,7 @@ describe('hubConnection', function () { protocol: protocol, logging: signalR.LogLevel.Trace }; - var hubConnection = new signalR.HubConnection(new signalR.HttpConnection(TESTHUBENDPOINT_URL, options), options); + var hubConnection = new signalR.HubConnection(TESTHUBENDPOINT_URL, options); hubConnection.start().then(function () { hubConnection.stream('ThrowException', errorMessage).subscribe({ @@ -132,7 +132,7 @@ describe('hubConnection', function () { protocol: protocol, logging: signalR.LogLevel.Trace }; - var hubConnection = new signalR.HubConnection(new signalR.HttpConnection(TESTHUBENDPOINT_URL, options), options); + var hubConnection = new signalR.HubConnection(TESTHUBENDPOINT_URL, options); var message = "你好 SignalR!"; @@ -170,7 +170,7 @@ describe('hubConnection', function () { protocol: protocol, logging: signalR.LogLevel.Trace }; - var hubConnection = new signalR.HubConnection(new signalR.HttpConnection('http://' + document.location.host + '/uncreatable', options), options); + var hubConnection = new signalR.HubConnection('http://' + document.location.host + '/uncreatable', options); hubConnection.onClosed = function (error) { expect(error.message).toMatch(errorRegex[signalR.TransportType[transportType]]); From 67b2b27ac9573d6b453cef1a53efbcc22048c12e Mon Sep 17 00:00:00 2001 From: Pawel Kadluczka Date: Mon, 11 Sep 2017 15:10:26 -0700 Subject: [PATCH 2/4] Enabling passing relative Url when creating connections --- .../Connection.spec.ts | 4 +++ .../HttpConnection.ts | 26 +++++++++++++++++-- .../wwwroot/js/hubConnectionTests.js | 2 +- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/client-ts/Microsoft.AspNetCore.SignalR.Client.TS.Tests/Connection.spec.ts b/client-ts/Microsoft.AspNetCore.SignalR.Client.TS.Tests/Connection.spec.ts index 00b3e597ee..70120c0790 100644 --- a/client-ts/Microsoft.AspNetCore.SignalR.Client.TS.Tests/Connection.spec.ts +++ b/client-ts/Microsoft.AspNetCore.SignalR.Client.TS.Tests/Connection.spec.ts @@ -9,6 +9,10 @@ import { ITransport, TransportType, TransferMode } from "../Microsoft.AspNetCore import { eachTransport } from "./Common"; describe("Connection", () => { + it("cannot be created with relative url if document object is not present", () => { + expect(() => new HttpConnection("/test")) + .toThrow(new Error("Cannot resolve '/test'.")); + }); it("starting connection fails if getting id fails", async (done) => { let options: IHttpConnectionOptions = { diff --git a/client-ts/Microsoft.AspNetCore.SignalR.Client.TS/HttpConnection.ts b/client-ts/Microsoft.AspNetCore.SignalR.Client.TS/HttpConnection.ts index 2549c72495..fcc76ce879 100644 --- a/client-ts/Microsoft.AspNetCore.SignalR.Client.TS/HttpConnection.ts +++ b/client-ts/Microsoft.AspNetCore.SignalR.Client.TS/HttpConnection.ts @@ -34,10 +34,10 @@ export class HttpConnection implements IConnection { readonly features: any = {}; constructor(url: string, options: IHttpConnectionOptions = {}) { - this.url = url; + this.logger = LoggerFactory.createLogger(options.logging); + this.url = this.resolveUrl(url); options = options || {}; this.httpClient = options.httpClient || new HttpClient(); - this.logger = LoggerFactory.createLogger(options.logging); this.connectionState = ConnectionState.Initial; this.options = options; } @@ -156,6 +156,28 @@ export class HttpConnection implements IConnection { } } + private resolveUrl(url: string) : string { + // startsWith is not supported in IE + if (url.lastIndexOf("https://", 0) === 0 || url.lastIndexOf("http://", 0) === 0) { + return url; + } + + if (typeof window === 'undefined') { + throw new Error(`Cannot resolve '${url}'.`); + } + + let parser = window.document.createElement("a"); + parser.href = url; + + let baseUrl = (!parser.protocol || parser.protocol === ":") + ? `${window.document.location.protocol}//${(parser.host || window.document.location.host)}` + : `${parser.protocol}//${parser.host}`; + + let normalizedUrl = baseUrl + url; + this.logger.log(LogLevel.Information, `Normalizing '${url}' to '${normalizedUrl}'`); + return normalizedUrl; + } + onDataReceived: DataReceived; onClosed: ConnectionClosed; } \ No newline at end of file diff --git a/client-ts/Microsoft.AspNetCore.SignalR.Test.Server/wwwroot/js/hubConnectionTests.js b/client-ts/Microsoft.AspNetCore.SignalR.Test.Server/wwwroot/js/hubConnectionTests.js index c53ba3d710..39e45f09aa 100644 --- a/client-ts/Microsoft.AspNetCore.SignalR.Test.Server/wwwroot/js/hubConnectionTests.js +++ b/client-ts/Microsoft.AspNetCore.SignalR.Test.Server/wwwroot/js/hubConnectionTests.js @@ -3,7 +3,7 @@ 'use strict'; -var TESTHUBENDPOINT_URL = 'http://' + document.location.host + '/testhub'; +var TESTHUBENDPOINT_URL = '/testhub'; describe('hubConnection', function () { eachTransportAndProtocol(function (transportType, protocol) { From 54c71c4c103e0406e5f6b8ee39c1a3e5e93b61de Mon Sep 17 00:00:00 2001 From: Pawel Kadluczka Date: Mon, 11 Sep 2017 16:26:10 -0700 Subject: [PATCH 3/4] Replacing Cache-Control header with nonce Makes long polling work on IE10 and IE9 --- .../Transports.ts | 6 +-- .../wwwroot/js/common.js | 12 ++++- .../wwwroot/js/connectionTests.js | 42 ++++++++-------- .../wwwroot/js/webSocketTests.js | 48 ++++++++++--------- 4 files changed, 60 insertions(+), 48 deletions(-) diff --git a/client-ts/Microsoft.AspNetCore.SignalR.Client.TS/Transports.ts b/client-ts/Microsoft.AspNetCore.SignalR.Client.TS/Transports.ts index fe740cf66f..47ab138b6e 100644 --- a/client-ts/Microsoft.AspNetCore.SignalR.Client.TS/Transports.ts +++ b/client-ts/Microsoft.AspNetCore.SignalR.Client.TS/Transports.ts @@ -247,12 +247,12 @@ export class LongPollingTransport implements ITransport { } this.pollXhr = pollXhr; - this.pollXhr.open("GET", url, true); + + this.pollXhr.open("GET", `${url}&_=${Date.now()}`, true); if (transferMode === TransferMode.Binary) { this.pollXhr.responseType = "arraybuffer"; } - // IE caches xhr requests - this.pollXhr.setRequestHeader("Cache-Control", "no-cache"); + // TODO: consider making timeout configurable this.pollXhr.timeout = 120000; this.pollXhr.send(); diff --git a/client-ts/Microsoft.AspNetCore.SignalR.Test.Server/wwwroot/js/common.js b/client-ts/Microsoft.AspNetCore.SignalR.Test.Server/wwwroot/js/common.js index eeae3d9c6b..237b46e6f9 100644 --- a/client-ts/Microsoft.AspNetCore.SignalR.Test.Server/wwwroot/js/common.js +++ b/client-ts/Microsoft.AspNetCore.SignalR.Test.Server/wwwroot/js/common.js @@ -6,7 +6,10 @@ var ECHOENDPOINT_URL = "http://" + document.location.host + "/echo"; function getTransportTypes() { - var transportTypes = [signalR.TransportType.WebSockets]; + var transportTypes = []; + if (typeof WebSocket !== "undefined") { + transportTypes.push(signalR.TransportType.WebSockets); + } if (typeof EventSource !== "undefined") { transportTypes.push(signalR.TransportType.ServerSentEvents); } @@ -22,7 +25,12 @@ function eachTransport(action) { } function eachTransportAndProtocol(action) { - var protocols = [new signalR.JsonHubProtocol(), new signalRMsgPack.MessagePackHubProtocol()]; + var protocols = [new signalR.JsonHubProtocol()]; + // IE9 does not support XmlHttpRequest advanced features so disable for now + // This can be enabled if we fix: https://github.com/aspnet/SignalR/issues/742 + if (typeof new XMLHttpRequest().responseType === "string") { + protocols.push(new signalRMsgPack.MessagePackHubProtocol()); + } getTransportTypes().forEach(function (t) { return protocols.forEach(function (p) { return action(t, p); diff --git a/client-ts/Microsoft.AspNetCore.SignalR.Test.Server/wwwroot/js/connectionTests.js b/client-ts/Microsoft.AspNetCore.SignalR.Test.Server/wwwroot/js/connectionTests.js index da387cc182..1f3ee9574b 100644 --- a/client-ts/Microsoft.AspNetCore.SignalR.Test.Server/wwwroot/js/connectionTests.js +++ b/client-ts/Microsoft.AspNetCore.SignalR.Test.Server/wwwroot/js/connectionTests.js @@ -4,30 +4,32 @@ "use strict"; describe('connection', function () { - it("can connect to the server without specifying transport explicitly", function (done) { - var message = "Hello World!"; - var connection = new signalR.HttpConnection(ECHOENDPOINT_URL); + if (typeof WebSocket !== 'undefined') { + it("can connect to the server without specifying transport explicitly", function (done) { + var message = "Hello World!"; + var connection = new signalR.HttpConnection(ECHOENDPOINT_URL); - var received = ""; - connection.onDataReceived = function (data) { - received += data; - if (data == message) { - connection.stop(); - } - }; + var received = ""; + connection.onDataReceived = function (data) { + received += data; + if (data == message) { + connection.stop(); + } + }; - connection.onClosed = function (error) { - expect(error).toBeUndefined(); - done(); - }; + connection.onClosed = function (error) { + expect(error).toBeUndefined(); + done(); + }; - connection.start().then(function () { - connection.send(message); - }).catch(function (e) { - fail(); - done(); + connection.start().then(function () { + connection.send(message); + }).catch(function (e) { + fail(); + done(); + }); }); - }); + } eachTransport(function (transportType) { it("over " + signalR.TransportType[transportType] + " can send and receive messages", function (done) { diff --git a/client-ts/Microsoft.AspNetCore.SignalR.Test.Server/wwwroot/js/webSocketTests.js b/client-ts/Microsoft.AspNetCore.SignalR.Test.Server/wwwroot/js/webSocketTests.js index 12a26d2e7c..6fb6bc86ef 100644 --- a/client-ts/Microsoft.AspNetCore.SignalR.Test.Server/wwwroot/js/webSocketTests.js +++ b/client-ts/Microsoft.AspNetCore.SignalR.Test.Server/wwwroot/js/webSocketTests.js @@ -3,33 +3,35 @@ 'use strict'; -describe('WebSockets', function () { - it('can be used to connect to SignalR', function (done) { - var message = "message"; +if (typeof WebSocket !== 'undefined') { + describe('WebSockets', function () { + it('can be used to connect to SignalR', function (done) { + var message = "message"; - var webSocket = new WebSocket(ECHOENDPOINT_URL.replace(/^http/, "ws")); + var webSocket = new WebSocket(ECHOENDPOINT_URL.replace(/^http/, "ws")); - webSocket.onopen = function () { - webSocket.send(message); - }; + webSocket.onopen = function () { + webSocket.send(message); + }; - var received = ""; - webSocket.onmessage = function (event) { - received += event.data; - if (received === message) { - webSocket.close(); - } - }; + var received = ""; + webSocket.onmessage = function (event) { + received += event.data; + if (received === message) { + webSocket.close(); + } + }; - webSocket.onclose = function (event) { - if (!event.wasClean) { - fail("connection closed with unexpected status code: " + event.code + " " + event.reason); - } + webSocket.onclose = function (event) { + if (!event.wasClean) { + fail("connection closed with unexpected status code: " + event.code + " " + event.reason); + } - // Jasmine doesn't like tests without expectations - expect(event.wasClean).toBe(true); + // Jasmine doesn't like tests without expectations + expect(event.wasClean).toBe(true); - done(); - }; + done(); + }; + }); }); -}); +} From abd669849d6ee7ef6b8854b6f3d8840757e14f47 Mon Sep 17 00:00:00 2001 From: Pawel Kadluczka Date: Mon, 11 Sep 2017 17:20:48 -0700 Subject: [PATCH 4/4] Making MsgPack work in IE10 --- client-ts/Microsoft.AspNetCore.SignalR.Client.TS/Formatters.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client-ts/Microsoft.AspNetCore.SignalR.Client.TS/Formatters.ts b/client-ts/Microsoft.AspNetCore.SignalR.Client.TS/Formatters.ts index adc794a884..d4edd4db41 100644 --- a/client-ts/Microsoft.AspNetCore.SignalR.Client.TS/Formatters.ts +++ b/client-ts/Microsoft.AspNetCore.SignalR.Client.TS/Formatters.ts @@ -22,7 +22,8 @@ export namespace TextMessageFormat { export namespace BinaryMessageFormat { export function write(output: Uint8Array): ArrayBuffer { - let size = output.byteLength; + // .byteLength does is undefined in IE10 + let size = output.byteLength || output.length; let buffer = new Uint8Array(size + 8); // javascript bitwise operators only support 32-bit integers