Merge branch 'release/2.1' into dev

This commit is contained in:
Andrew Stanton-Nurse 2018-04-19 12:11:28 -07:00
commit 29ddc6a3e5
17 changed files with 512 additions and 196 deletions

View File

@ -22,8 +22,8 @@
"build:rollup": "node ../node_modules/rollup/bin/rollup -c",
"pretest": "npm run build",
"test": "dotnet build && npm run test-only",
"test-only": "ts-node --project ./selenium/tsconfig.json ./selenium/run-tests.ts",
"ci-test": "ts-node --project ./selenium/tsconfig.json ./selenium/run-ci-tests.ts"
"test-only": "ts-node --project ./selenium/tsconfig-selenium.json ./selenium/run-tests.ts",
"ci-test": "ts-node --project ./selenium/tsconfig-selenium.json ./selenium/run-ci-tests.ts"
},
"author": "",
"license": "Apache-2.0"

View File

@ -1,7 +1,7 @@
// 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 { DefaultHttpClient, HttpClient, HttpRequest, HttpResponse, HttpTransportType, HubConnection, IHubConnectionOptions, IStreamSubscriber, JsonHubProtocol, LogLevel } from "@aspnet/signalr";
import { DefaultHttpClient, HttpClient, HttpRequest, HttpResponse, HttpTransportType, HubConnection, HubConnectionBuilder, IHttpConnectionOptions, IStreamSubscriber, JsonHubProtocol, LogLevel } from "@aspnet/signalr";
import { MessagePackHubProtocol } from "@aspnet/signalr-protocol-msgpack";
import { eachTransport, eachTransportAndProtocol } from "./Common";
@ -10,25 +10,35 @@ import { TestLogger } from "./TestLogger";
const TESTHUBENDPOINT_URL = "/testhub";
const TESTHUB_NOWEBSOCKETS_ENDPOINT_URL = "/testhub-nowebsockets";
const commonOptions: IHubConnectionOptions = {
logMessageContent: true,
logger: TestLogger.instance,
};
// On slower CI machines, these tests sometimes take longer than 5s
jasmine.DEFAULT_TIMEOUT_INTERVAL = 10 * 1000;
const commonOptions: IHttpConnectionOptions = {
logMessageContent: true,
};
function getConnectionBuilder(transportType?: HttpTransportType, url?: string, options?: IHttpConnectionOptions): HubConnectionBuilder {
let actualOptions: IHttpConnectionOptions = options || {};
if (transportType) {
actualOptions.transport = transportType;
}
actualOptions = { ...actualOptions, ...commonOptions };
return new HubConnectionBuilder()
.configureLogging(TestLogger.instance)
.withUrl(url || TESTHUBENDPOINT_URL, actualOptions);
}
describe("hubConnection", () => {
eachTransportAndProtocol((transportType, protocol) => {
describe("using " + protocol.name + " over " + HttpTransportType[transportType] + " transport", async () => {
it("can invoke server method and receive result", (done) => {
const message = "你好,世界!";
const hubConnection = new HubConnection(TESTHUBENDPOINT_URL, {
...commonOptions,
protocol,
transport: transportType,
});
const hubConnection = getConnectionBuilder(transportType)
.withHubProtocol(protocol)
.build();
hubConnection.onclose((error) => {
expect(error).toBeUndefined();
done();
@ -51,11 +61,10 @@ describe("hubConnection", () => {
it("can invoke server method non-blocking and not receive result", (done) => {
const message = "你好,世界!";
const hubConnection = new HubConnection(TESTHUBENDPOINT_URL, {
...commonOptions,
protocol,
transport: transportType,
});
const hubConnection = getConnectionBuilder(transportType)
.withHubProtocol(protocol)
.build();
hubConnection.onclose((error) => {
expect(error).toBe(undefined);
done();
@ -74,11 +83,9 @@ describe("hubConnection", () => {
});
it("can invoke server method structural object and receive structural result", (done) => {
const hubConnection = new HubConnection(TESTHUBENDPOINT_URL, {
...commonOptions,
protocol,
transport: transportType,
});
const hubConnection = getConnectionBuilder(transportType)
.withHubProtocol(protocol)
.build();
hubConnection.on("CustomObject", (customObject) => {
expect(customObject.Name).toBe("test");
@ -100,11 +107,9 @@ describe("hubConnection", () => {
});
it("can stream server method and receive result", (done) => {
const hubConnection = new HubConnection(TESTHUBENDPOINT_URL, {
...commonOptions,
protocol,
transport: transportType,
});
const hubConnection = getConnectionBuilder(transportType)
.withHubProtocol(protocol)
.build();
hubConnection.onclose((error) => {
expect(error).toBe(undefined);
@ -134,11 +139,9 @@ describe("hubConnection", () => {
it("rethrows an exception from the server when invoking", (done) => {
const errorMessage = "An unexpected error occurred invoking 'ThrowException' on the server. InvalidOperationException: An error occurred.";
const hubConnection = new HubConnection(TESTHUBENDPOINT_URL, {
...commonOptions,
protocol,
transport: transportType,
});
const hubConnection = getConnectionBuilder(transportType)
.withHubProtocol(protocol)
.build();
hubConnection.start().then(() => {
hubConnection.invoke("ThrowException", "An error occurred.").then(() => {
@ -158,11 +161,9 @@ describe("hubConnection", () => {
});
it("throws an exception when invoking streaming method with invoke", (done) => {
const hubConnection = new HubConnection(TESTHUBENDPOINT_URL, {
...commonOptions,
protocol,
transport: transportType,
});
const hubConnection = getConnectionBuilder(transportType)
.withHubProtocol(protocol)
.build();
hubConnection.start().then(() => {
hubConnection.invoke("EmptyStream").then(() => {
@ -182,11 +183,9 @@ describe("hubConnection", () => {
});
it("throws an exception when receiving a streaming result for method called with invoke", (done) => {
const hubConnection = new HubConnection(TESTHUBENDPOINT_URL, {
...commonOptions,
protocol,
transport: transportType,
});
const hubConnection = getConnectionBuilder(transportType)
.withHubProtocol(protocol)
.build();
hubConnection.start().then(() => {
hubConnection.invoke("Stream").then(() => {
@ -207,11 +206,9 @@ describe("hubConnection", () => {
it("rethrows an exception from the server when streaming", (done) => {
const errorMessage = "An unexpected error occurred invoking 'StreamThrowException' on the server. InvalidOperationException: An error occurred.";
const hubConnection = new HubConnection(TESTHUBENDPOINT_URL, {
...commonOptions,
protocol,
transport: transportType,
});
const hubConnection = getConnectionBuilder(transportType)
.withHubProtocol(protocol)
.build();
hubConnection.start().then(() => {
hubConnection.stream("StreamThrowException", "An error occurred.").subscribe({
@ -236,11 +233,9 @@ describe("hubConnection", () => {
});
it("throws an exception when invoking hub method with stream", (done) => {
const hubConnection = new HubConnection(TESTHUBENDPOINT_URL, {
...commonOptions,
protocol,
transport: transportType,
});
const hubConnection = getConnectionBuilder(transportType)
.withHubProtocol(protocol)
.build();
hubConnection.start().then(() => {
hubConnection.stream("Echo", "42").subscribe({
@ -265,11 +260,9 @@ describe("hubConnection", () => {
});
it("can receive server calls", (done) => {
const hubConnection = new HubConnection(TESTHUBENDPOINT_URL, {
...commonOptions,
protocol,
transport: transportType,
});
const hubConnection = getConnectionBuilder(transportType)
.withHubProtocol(protocol)
.build();
const message = "你好 SignalR";
@ -297,11 +290,9 @@ describe("hubConnection", () => {
});
it("can receive server calls without rebinding handler when restarted", (done) => {
const hubConnection = new HubConnection(TESTHUBENDPOINT_URL, {
...commonOptions,
protocol,
transport: transportType,
});
const hubConnection = getConnectionBuilder(transportType)
.withHubProtocol(protocol)
.build();
const message = "你好 SignalR";
@ -354,11 +345,9 @@ describe("hubConnection", () => {
});
it("closed with error if hub cannot be created", (done) => {
const hubConnection = new HubConnection("http://" + document.location.host + "/uncreatable", {
...commonOptions,
protocol,
transport: transportType,
});
const hubConnection = getConnectionBuilder(transportType, "http://" + document.location.host + "/uncreatable")
.withHubProtocol(protocol)
.build();
hubConnection.onclose((error) => {
expect(error.message).toEqual("Server returned an error on close: Connection closed with an error. InvalidOperationException: Unable to resolve service for type 'System.Object' while attempting to activate 'FunctionalTests.UncreatableHub'.");
@ -368,11 +357,10 @@ describe("hubConnection", () => {
});
it("can handle different types", (done) => {
const hubConnection = new HubConnection(TESTHUBENDPOINT_URL, {
...commonOptions,
protocol,
transport: transportType,
});
const hubConnection = getConnectionBuilder(transportType)
.withHubProtocol(protocol)
.build();
hubConnection.onclose((error) => {
expect(error).toBe(undefined);
done();
@ -412,11 +400,10 @@ describe("hubConnection", () => {
});
it("can receive different types", (done) => {
const hubConnection = new HubConnection(TESTHUBENDPOINT_URL, {
...commonOptions,
protocol,
transport: transportType,
});
const hubConnection = getConnectionBuilder(transportType)
.withHubProtocol(protocol)
.build();
hubConnection.onclose((error) => {
expect(error).toBe(undefined);
done();
@ -458,11 +445,9 @@ describe("hubConnection", () => {
it("can be restarted", (done) => {
const message = "你好,世界!";
const hubConnection = new HubConnection(TESTHUBENDPOINT_URL, {
...commonOptions,
protocol,
transport: transportType,
});
const hubConnection = getConnectionBuilder(transportType)
.withHubProtocol(protocol)
.build();
let closeCount = 0;
hubConnection.onclose((error) => {
@ -512,11 +497,11 @@ describe("hubConnection", () => {
try {
const jwtToken = await getJwtToken("http://" + document.location.host + "/generateJwtToken");
const hubConnection = new HubConnection("/authorizedhub", {
const hubConnection = getConnectionBuilder(transportType, "/authorizedhub", {
accessTokenFactory: () => jwtToken,
...commonOptions,
transport: transportType,
});
}).build();
hubConnection.onclose((error) => {
expect(error).toBe(undefined);
done();
@ -539,11 +524,10 @@ describe("hubConnection", () => {
const message = "你好,世界!";
try {
const hubConnection = new HubConnection("/authorizedhub", {
const hubConnection = getConnectionBuilder(transportType, "/authorizedhub", {
accessTokenFactory: () => getJwtToken("http://" + document.location.host + "/generateJwtToken"),
...commonOptions,
transport: transportType,
});
}).build();
hubConnection.onclose((error) => {
expect(error).toBe(undefined);
done();
@ -566,11 +550,10 @@ describe("hubConnection", () => {
const message = "你好,世界!";
try {
const hubConnection = new HubConnection("/authorizedhub", {
const hubConnection = getConnectionBuilder(transportType, "/authorizedhub", {
accessTokenFactory: () => getJwtToken("http://" + document.location.host + "/generateJwtToken"),
...commonOptions,
transport: transportType,
});
}).build();
hubConnection.onclose((error) => {
expect(error).toBe(undefined);
done();
@ -591,11 +574,8 @@ describe("hubConnection", () => {
if (transportType !== HttpTransportType.LongPolling) {
it("terminates if no messages received within timeout interval", (done) => {
const hubConnection = new HubConnection(TESTHUBENDPOINT_URL, {
...commonOptions,
timeoutInMilliseconds: 100,
transport: transportType,
});
const hubConnection = getConnectionBuilder(transportType).build();
hubConnection.serverTimeoutInMilliseconds = 100;
const timeout = setTimeout(200, () => {
fail("Server timeout did not fire within expected interval");
@ -615,10 +595,9 @@ describe("hubConnection", () => {
if (typeof EventSource !== "undefined") {
it("allows Server-Sent Events when negotiating for JSON protocol", async (done) => {
const hubConnection = new HubConnection(TESTHUB_NOWEBSOCKETS_ENDPOINT_URL, {
...commonOptions,
protocol: new JsonHubProtocol(),
});
const hubConnection = getConnectionBuilder(undefined, TESTHUB_NOWEBSOCKETS_ENDPOINT_URL)
.withHubProtocol(new JsonHubProtocol())
.build();
try {
await hubConnection.start();
@ -633,10 +612,9 @@ describe("hubConnection", () => {
}
it("skips Server-Sent Events when negotiating for MessagePack protocol", async (done) => {
const hubConnection = new HubConnection(TESTHUB_NOWEBSOCKETS_ENDPOINT_URL, {
...commonOptions,
protocol: new MessagePackHubProtocol(),
});
const hubConnection = getConnectionBuilder(undefined, TESTHUB_NOWEBSOCKETS_ENDPOINT_URL)
.withHubProtocol(new MessagePackHubProtocol())
.build();
try {
await hubConnection.start();
@ -657,10 +635,9 @@ describe("hubConnection", () => {
throw new Error("Kick rocks");
};
const hubConnection = new HubConnection(TESTHUBENDPOINT_URL, {
...commonOptions,
protocol: new JsonHubProtocol(),
});
const hubConnection = getConnectionBuilder()
.withHubProtocol(new JsonHubProtocol())
.build();
try {
await hubConnection.start();
@ -693,10 +670,10 @@ describe("hubConnection", () => {
}
const testClient = new TestClient();
const hubConnection = new HubConnection(TESTHUBENDPOINT_URL, {
...commonOptions,
const hubConnection = getConnectionBuilder(HttpTransportType.LongPolling, TESTHUBENDPOINT_URL, {
httpClient: testClient,
});
}).build();
try {
await hubConnection.start();

View File

@ -1,27 +1,27 @@
// 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 { HubConnection } from "../src/HubConnection";
import { IHubConnectionOptions } from "../src/HubConnection";
import { HubConnection, JsonHubProtocol } from "../src/HubConnection";
import { IConnection } from "../src/IConnection";
import { HubMessage, IHubProtocol, MessageType } from "../src/IHubProtocol";
import { ILogger, LogLevel } from "../src/ILogger";
import { HttpTransportType, ITransport, TransferFormat } from "../src/ITransport";
import { NullLogger } from "../src/Loggers";
import { IStreamSubscriber } from "../src/Stream";
import { TextMessageFormat } from "../src/TextMessageFormat";
import { asyncit as it, captureException, delay, PromiseSource } from "./Utils";
const commonOptions: IHubConnectionOptions = {
logger: null,
};
function createHubConnection(connection: IConnection, logger?: ILogger, protocol?: IHubProtocol) {
return new HubConnection(connection, logger || NullLogger.instance, protocol || new JsonHubProtocol());
}
describe("HubConnection", () => {
describe("start", () => {
it("sends negotiation message", async () => {
const connection = new TestConnection();
const hubConnection = new HubConnection(connection, commonOptions);
const hubConnection = createHubConnection(connection);
await hubConnection.start();
expect(connection.sentData.length).toBe(1);
expect(JSON.parse(connection.sentData[0])).toEqual({
@ -36,7 +36,7 @@ describe("HubConnection", () => {
it("sends a non blocking invocation", async () => {
const connection = new TestConnection();
const hubConnection = new HubConnection(connection, commonOptions);
const hubConnection = createHubConnection(connection);
const invokePromise = hubConnection.send("testMethod", "arg", 42)
.catch((_) => { }); // Suppress exception and unhandled promise rejection warning.
@ -60,7 +60,7 @@ describe("HubConnection", () => {
it("sends an invocation", async () => {
const connection = new TestConnection();
const hubConnection = new HubConnection(connection, commonOptions);
const hubConnection = createHubConnection(connection);
const invokePromise = hubConnection.invoke("testMethod", "arg", 42)
.catch((_) => { }); // Suppress exception and unhandled promise rejection warning.
@ -89,7 +89,7 @@ describe("HubConnection", () => {
};
const connection = new TestConnection();
const hubConnection = new HubConnection(connection, { logger: null, protocol: mockProtocol });
const hubConnection = createHubConnection(connection, null, mockProtocol);
const data = "{}" + TextMessageFormat.RecordSeparator;
@ -108,7 +108,7 @@ describe("HubConnection", () => {
};
const connection = new TestConnection();
const hubConnection = new HubConnection(connection, { logger: null, protocol: mockProtocol });
const hubConnection = createHubConnection(connection, null, mockProtocol);
// handshake response + message separator
const data = [0x7b, 0x7d, 0x1e];
@ -126,7 +126,7 @@ describe("HubConnection", () => {
mockProtocol.onreceive = (d) => receivedProcotolData = d as ArrayBuffer;
const connection = new TestConnection();
const hubConnection = new HubConnection(connection, { logger: null, protocol: mockProtocol });
const hubConnection = createHubConnection(connection, null, mockProtocol);
// handshake response + message separator + message pack message
const data = [
@ -151,7 +151,7 @@ describe("HubConnection", () => {
mockProtocol.onreceive = (d) => receivedProcotolData = d as string;
const connection = new TestConnection();
const hubConnection = new HubConnection(connection, { logger: null, protocol: mockProtocol });
const hubConnection = createHubConnection(connection, null, mockProtocol);
const data = "{}" + TextMessageFormat.RecordSeparator + "{\"type\":6}" + TextMessageFormat.RecordSeparator;
@ -162,7 +162,7 @@ describe("HubConnection", () => {
it("rejects the promise when an error is received", async () => {
const connection = new TestConnection();
const hubConnection = new HubConnection(connection, commonOptions);
const hubConnection = createHubConnection(connection);
connection.receiveHandshakeResponse();
const invokePromise = hubConnection.invoke("testMethod", "arg", 42);
@ -175,7 +175,7 @@ describe("HubConnection", () => {
it("resolves the promise when a result is received", async () => {
const connection = new TestConnection();
const hubConnection = new HubConnection(connection, commonOptions);
const hubConnection = createHubConnection(connection);
connection.receiveHandshakeResponse();
const invokePromise = hubConnection.invoke("testMethod", "arg", 42);
@ -188,7 +188,7 @@ describe("HubConnection", () => {
it("completes pending invocations when stopped", async () => {
const connection = new TestConnection();
const hubConnection = new HubConnection(connection, commonOptions);
const hubConnection = createHubConnection(connection);
connection.receiveHandshakeResponse();
@ -202,7 +202,7 @@ describe("HubConnection", () => {
it("completes pending invocations when connection is lost", async () => {
const connection = new TestConnection();
const hubConnection = new HubConnection(connection, commonOptions);
const hubConnection = createHubConnection(connection);
connection.receiveHandshakeResponse();
@ -226,7 +226,7 @@ describe("HubConnection", () => {
},
} as ILogger;
const connection = new TestConnection();
const hubConnection = new HubConnection(connection, { logger });
const hubConnection = createHubConnection(connection, logger);
connection.receiveHandshakeResponse();
@ -250,7 +250,7 @@ describe("HubConnection", () => {
},
} as ILogger;
const connection = new TestConnection();
const hubConnection = new HubConnection(connection, { logger });
const hubConnection = createHubConnection(connection, logger);
connection.receiveHandshakeResponse();
@ -271,7 +271,7 @@ describe("HubConnection", () => {
it("all handlers can be unregistered with just the method name", async () => {
const connection = new TestConnection();
const hubConnection = new HubConnection(connection, commonOptions);
const hubConnection = createHubConnection(connection);
connection.receiveHandshakeResponse();
@ -304,7 +304,7 @@ describe("HubConnection", () => {
it("a single handler can be unregistered with the method name and handler", async () => {
const connection = new TestConnection();
const hubConnection = new HubConnection(connection, commonOptions);
const hubConnection = createHubConnection(connection);
connection.receiveHandshakeResponse();
@ -337,7 +337,7 @@ describe("HubConnection", () => {
it("can't register the same handler multiple times", async () => {
const connection = new TestConnection();
const hubConnection = new HubConnection(connection, commonOptions);
const hubConnection = createHubConnection(connection);
connection.receiveHandshakeResponse();
@ -359,7 +359,7 @@ describe("HubConnection", () => {
it("callback invoked when servers invokes a method on the client", async () => {
const connection = new TestConnection();
const hubConnection = new HubConnection(connection, commonOptions);
const hubConnection = createHubConnection(connection);
connection.receiveHandshakeResponse();
@ -379,7 +379,7 @@ describe("HubConnection", () => {
it("stop on handshake error", async () => {
const connection = new TestConnection();
const hubConnection = new HubConnection(connection, commonOptions);
const hubConnection = createHubConnection(connection);
let closeError: Error = null;
hubConnection.onclose((e) => closeError = e);
@ -391,7 +391,7 @@ describe("HubConnection", () => {
it("stop on close message", async () => {
const connection = new TestConnection();
const hubConnection = new HubConnection(connection, commonOptions);
const hubConnection = createHubConnection(connection);
let isClosed = false;
let closeError: Error = null;
@ -412,7 +412,7 @@ describe("HubConnection", () => {
it("stop on error close message", async () => {
const connection = new TestConnection();
const hubConnection = new HubConnection(connection, commonOptions);
const hubConnection = createHubConnection(connection);
let isClosed = false;
let closeError: Error = null;
@ -434,7 +434,7 @@ describe("HubConnection", () => {
it("can have multiple callbacks", async () => {
const connection = new TestConnection();
const hubConnection = new HubConnection(connection, commonOptions);
const hubConnection = createHubConnection(connection);
connection.receiveHandshakeResponse();
@ -457,7 +457,7 @@ describe("HubConnection", () => {
it("can unsubscribe from on", async () => {
const connection = new TestConnection();
const hubConnection = new HubConnection(connection, commonOptions);
const hubConnection = createHubConnection(connection);
connection.receiveHandshakeResponse();
@ -488,7 +488,7 @@ describe("HubConnection", () => {
it("unsubscribing from non-existing callbacks no-ops", async () => {
const connection = new TestConnection();
const hubConnection = new HubConnection(connection, commonOptions);
const hubConnection = createHubConnection(connection);
hubConnection.off("_", () => { });
hubConnection.on("message", (t) => { });
@ -507,7 +507,7 @@ describe("HubConnection", () => {
} as ILogger;
const connection = new TestConnection();
const hubConnection = new HubConnection(connection, { logger });
const hubConnection = createHubConnection(connection, logger);
connection.receiveHandshakeResponse();
@ -542,7 +542,7 @@ describe("HubConnection", () => {
it("sends an invocation", async () => {
const connection = new TestConnection();
const hubConnection = new HubConnection(connection, commonOptions);
const hubConnection = createHubConnection(connection);
const invokePromise = hubConnection.stream("testStream", "arg", 42);
// Verify the message is sent
@ -563,7 +563,7 @@ describe("HubConnection", () => {
it("completes with an error when an error is yielded", async () => {
const connection = new TestConnection();
const hubConnection = new HubConnection(connection, commonOptions);
const hubConnection = createHubConnection(connection);
connection.receiveHandshakeResponse();
@ -579,7 +579,7 @@ describe("HubConnection", () => {
it("completes the observer when a completion is received", async () => {
const connection = new TestConnection();
const hubConnection = new HubConnection(connection, commonOptions);
const hubConnection = createHubConnection(connection);
connection.receiveHandshakeResponse();
@ -595,7 +595,7 @@ describe("HubConnection", () => {
it("completes pending streams when stopped", async () => {
const connection = new TestConnection();
const hubConnection = new HubConnection(connection, commonOptions);
const hubConnection = createHubConnection(connection);
const observer = new TestObserver();
hubConnection.stream<any>("testMethod")
.subscribe(observer);
@ -608,7 +608,7 @@ describe("HubConnection", () => {
it("completes pending streams when connection is lost", async () => {
const connection = new TestConnection();
const hubConnection = new HubConnection(connection, commonOptions);
const hubConnection = createHubConnection(connection);
const observer = new TestObserver();
hubConnection.stream<any>("testMethod")
.subscribe(observer);
@ -622,7 +622,7 @@ describe("HubConnection", () => {
it("yields items as they arrive", async () => {
const connection = new TestConnection();
const hubConnection = new HubConnection(connection, commonOptions);
const hubConnection = createHubConnection(connection);
connection.receiveHandshakeResponse();
@ -646,7 +646,7 @@ describe("HubConnection", () => {
it("does not require error function registered", async () => {
const connection = new TestConnection();
const hubConnection = new HubConnection(connection, commonOptions);
const hubConnection = createHubConnection(connection);
const observer = hubConnection.stream("testMethod").subscribe(NullSubscriber.instance);
// Typically this would be called by the transport
@ -657,7 +657,7 @@ describe("HubConnection", () => {
it("does not require complete function registered", async () => {
const connection = new TestConnection();
const hubConnection = new HubConnection(connection, commonOptions);
const hubConnection = createHubConnection(connection);
const observer = hubConnection.stream("testMethod").subscribe(NullSubscriber.instance);
// Send completion to trigger observer.complete()
@ -667,7 +667,7 @@ describe("HubConnection", () => {
it("can be canceled", () => {
const connection = new TestConnection();
const hubConnection = new HubConnection(connection, commonOptions);
const hubConnection = createHubConnection(connection);
connection.receiveHandshakeResponse();
@ -696,7 +696,7 @@ describe("HubConnection", () => {
describe("onClose", () => {
it("can have multiple callbacks", async () => {
const connection = new TestConnection();
const hubConnection = new HubConnection(connection, commonOptions);
const hubConnection = createHubConnection(connection);
let invocations = 0;
hubConnection.onclose((e) => invocations++);
hubConnection.onclose((e) => invocations++);
@ -707,7 +707,7 @@ describe("HubConnection", () => {
it("callbacks receive error", async () => {
const connection = new TestConnection();
const hubConnection = new HubConnection(connection, commonOptions);
const hubConnection = createHubConnection(connection);
let error: Error;
hubConnection.onclose((e) => error = e);
@ -718,7 +718,7 @@ describe("HubConnection", () => {
it("ignores null callbacks", async () => {
const connection = new TestConnection();
const hubConnection = new HubConnection(connection, commonOptions);
const hubConnection = createHubConnection(connection);
hubConnection.onclose(null);
hubConnection.onclose(undefined);
// Typically this would be called by the transport
@ -732,7 +732,7 @@ describe("HubConnection", () => {
// Receive the ping mid-invocation so we can see that the rest of the flow works fine
const connection = new TestConnection();
const hubConnection = new HubConnection(connection, commonOptions);
const hubConnection = createHubConnection(connection);
const invokePromise = hubConnection.invoke("testMethod", "arg", 42);
connection.receive({ type: MessageType.Ping });
@ -743,7 +743,8 @@ describe("HubConnection", () => {
it("does not terminate if messages are received", async () => {
const connection = new TestConnection();
const hubConnection = new HubConnection(connection, { ...commonOptions, timeoutInMilliseconds: 100 });
const hubConnection = createHubConnection(connection);
hubConnection.serverTimeoutInMilliseconds = 100;
const p = new PromiseSource<Error>();
hubConnection.onclose((e) => p.resolve(e));
@ -768,7 +769,8 @@ describe("HubConnection", () => {
it("does not timeout if message was received before HubConnection.start", async () => {
const connection = new TestConnection();
const hubConnection = new HubConnection(connection, { ...commonOptions, timeoutInMilliseconds: 100 });
const hubConnection = createHubConnection(connection);
hubConnection.serverTimeoutInMilliseconds = 100;
const p = new PromiseSource<Error>();
hubConnection.onclose((e) => p.resolve(e));
@ -795,7 +797,8 @@ describe("HubConnection", () => {
it("terminates if no messages received within timeout interval", async () => {
const connection = new TestConnection();
const hubConnection = new HubConnection(connection, { ...commonOptions, timeoutInMilliseconds: 100 });
const hubConnection = createHubConnection(connection);
hubConnection.serverTimeoutInMilliseconds = 100;
const p = new PromiseSource<Error>();
hubConnection.onclose((e) => p.resolve(e));

View File

@ -0,0 +1,245 @@
// 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 { HubConnectionBuilder } from "../src/HubConnectionBuilder";
import { HubConnection } from "../src";
import { HttpRequest, HttpResponse } from "../src/HttpClient";
import { IHttpConnectionOptions } from "../src/HttpConnection";
import { HubMessage, IHubProtocol } from "../src/IHubProtocol";
import { ILogger, LogLevel } from "../src/ILogger";
import { TransferFormat, HttpTransportType } from "../src/ITransport";
import { NullLogger } from "../src/Loggers";
import { TestHttpClient } from "./TestHttpClient";
import { asyncit as it, PromiseSource } from "./Utils";
const allTransportsNegotiateResponse = {
availableTransports: [
{ transport: "WebSockets", transferFormats: ["Text", "Binary"] },
{ transport: "ServerSentEvents", transferFormats: ["Text"] },
{ transport: "LongPolling", transferFormats: ["Text", "Binary"] },
],
connectionId: "abc123",
};
const longPollingNegotiateResponse = {
availableTransports: [
{ transport: "LongPolling", transferFormats: ["Text", "Binary"] },
],
connectionId: "abc123",
};
const commonHttpOptions: IHttpConnectionOptions = {
logMessageContent: true,
};
describe("HubConnectionBuilder", () => {
eachMissingValue((val, name) => {
it(`configureLogging throws if logger is ${name}`, () => {
const builder = new HubConnectionBuilder();
expect(() => builder.configureLogging(val)).toThrow(new Error("The 'logging' argument is required."));
});
it(`withUrl throws if url is ${name}`, () => {
const builder = new HubConnectionBuilder();
expect(() => builder.withUrl(val)).toThrow(new Error("The 'url' argument is required."));
});
it(`withHubProtocol throws if protocol is ${name}`, () => {
const builder = new HubConnectionBuilder();
expect(() => builder.withHubProtocol(val)).toThrow(new Error("The 'protocol' argument is required."));
});
});
it("builds HubConnection with HttpConnection using provided URL", async () => {
const pollSent = new PromiseSource<HttpRequest>();
const pollCompleted = new PromiseSource<HttpResponse>();
const testClient = createTestClient(pollSent, pollCompleted.promise)
.on("POST", "http://example.com?id=abc123", (req) => {
// Respond from the poll with the handshake response
pollCompleted.resolve(new HttpResponse(204, "No Content", "{}"));
return new HttpResponse(202);
});
const connection = createConnectionBuilder()
.withUrl("http://example.com", {
...commonHttpOptions,
httpClient: testClient,
})
.build();
// Start the connection
const closed = makeClosedPromise(connection);
await connection.start();
const pollRequest = await pollSent.promise;
expect(pollRequest.url).toMatch(/http:\/\/example.com\?id=abc123.*/);
await closed;
});
it("can configure transport type", async () => {
const protocol = new TestProtocol();
const pollSent = new PromiseSource<HttpRequest>();
const pollCompleted = new PromiseSource<HttpResponse>();
const negotiateReceived = new PromiseSource<HttpRequest>();
const testClient = createTestClient(pollSent, pollCompleted.promise, allTransportsNegotiateResponse);
const builder = createConnectionBuilder()
.withUrl("http://example.com", HttpTransportType.WebSockets)
.withHubProtocol(protocol);
expect(builder.httpConnectionOptions.transport).toBe(HttpTransportType.WebSockets);
});
it("can configure hub protocol", async () => {
const protocol = new TestProtocol();
const pollSent = new PromiseSource<HttpRequest>();
const pollCompleted = new PromiseSource<HttpResponse>();
const negotiateReceived = new PromiseSource<HttpRequest>();
const testClient = createTestClient(pollSent, pollCompleted.promise)
.on("POST", "http://example.com?id=abc123", (req) => {
// Respond from the poll with the handshake response
negotiateReceived.resolve(req);
pollCompleted.resolve(new HttpResponse(204, "No Content", "{}"));
return new HttpResponse(202);
});
const connection = createConnectionBuilder()
.withUrl("http://example.com", {
...commonHttpOptions,
httpClient: testClient,
})
.withHubProtocol(protocol)
.build();
// Start the connection
const closed = makeClosedPromise(connection);
await connection.start();
const negotiateRequest = await negotiateReceived.promise;
expect(negotiateRequest.content).toBe(`{"protocol":"${protocol.name}","version":1}\x1E`);
await closed;
});
it("allows logger to be replaced", async () => {
let loggedMessages = 0;
const logger = {
log() {
loggedMessages += 1;
}
}
const connection = createConnectionBuilder(logger)
.withUrl("http://example.com")
.build();
try {
await connection.start();
} catch {
// Ignore failures
}
expect(loggedMessages).toBeGreaterThan(0);
});
it("uses logger for both HttpConnection and HubConnection", async () => {
const logger = new CaptureLogger();
const connection = createConnectionBuilder(logger)
.withUrl("http://example.com")
.build();
try {
await connection.start();
} catch {
// Ignore failures
}
// A HubConnection message
expect(logger.messages).toContain("Starting HubConnection.");
// An HttpConnection message
expect(logger.messages).toContain("Starting connection with transfer format 'Text'.");
});
it("does not replace HttpConnectionOptions logger if provided", async () => {
const hubConnectionLogger = new CaptureLogger();
const httpConnectionLogger = new CaptureLogger();
const connection = createConnectionBuilder(hubConnectionLogger)
.withUrl("http://example.com", { logger: httpConnectionLogger })
.build();
try {
await connection.start();
} catch {
// Ignore failures
}
// A HubConnection message
expect(hubConnectionLogger.messages).toContain("Starting HubConnection.");
expect(httpConnectionLogger.messages).not.toContain("Starting HubConnection.");
// An HttpConnection message
expect(httpConnectionLogger.messages).toContain("Starting connection with transfer format 'Text'.");
expect(hubConnectionLogger.messages).not.toContain("Starting connection with transfer format 'Text'.");
});
});
class CaptureLogger implements ILogger {
public readonly messages: string[] = [];
public log(logLevel: LogLevel, message: string): void {
this.messages.push(message);
}
}
class TestProtocol implements IHubProtocol {
public name: string = "test";
public version: number = 1;
public transferFormat: TransferFormat = TransferFormat.Text;
public parseMessages(input: any, logger: ILogger): HubMessage[] {
throw new Error("Method not implemented.");
}
public writeMessage(message: HubMessage) {
throw new Error("Method not implemented.");
}
}
function createConnectionBuilder(logger?: ILogger | LogLevel): HubConnectionBuilder {
// We don't want to spam test output with logs. This can be changed as needed
return new HubConnectionBuilder()
.configureLogging(logger || NullLogger.instance);
}
function createTestClient(pollSent: PromiseSource<HttpRequest>, pollCompleted: Promise<HttpResponse>, negotiateResponse?: any): TestHttpClient {
let firstRequest = true;
return new TestHttpClient()
.on("POST", "http://example.com/negotiate", () => negotiateResponse || longPollingNegotiateResponse)
.on("GET", /http:\/\/example.com\?id=abc123&_=.*/, (req) => {
if (firstRequest) {
firstRequest = false;
return new HttpResponse(200);
} else {
pollSent.resolve(req);
return pollCompleted;
}
});
}
function makeClosedPromise(connection: HubConnection): Promise<void> {
const closed = new PromiseSource();
connection.onclose((error) => {
if (error) {
closed.reject(error);
} else {
closed.resolve();
}
});
return closed.promise;
}
function eachMissingValue(callback: (val: undefined | null, name: string) => void) {
callback(null, "null");
callback(undefined, "undefined");
}

View File

@ -3,7 +3,7 @@
import { HttpClient, HttpRequest, HttpResponse } from "../src/HttpClient";
type TestHttpHandlerResult = any;
type TestHttpHandlerResult = string | HttpResponse | any;
export type TestHttpHandler = (request: HttpRequest, next?: (request: HttpRequest) => Promise<HttpResponse>) => Promise<TestHttpHandlerResult> | TestHttpHandlerResult;
export class TestHttpClient extends HttpClient {

View File

@ -38,7 +38,7 @@ export function delay(durationInMilliseconds: number): Promise<void> {
return source.promise;
}
export class PromiseSource<T> {
export class PromiseSource<T = void> {
public promise: Promise<T>;
private resolver: (value?: T | PromiseLike<T>) => void;

View File

@ -16,12 +16,14 @@ export interface HttpRequest {
}
export class HttpResponse {
constructor(statusCode: number);
constructor(statusCode: number, statusText: string);
constructor(statusCode: number, statusText: string, content: string);
constructor(statusCode: number, statusText: string, content: ArrayBuffer);
constructor(
public readonly statusCode: number,
public readonly statusText: string,
public readonly content: string | ArrayBuffer) {
public readonly statusText?: string,
public readonly content?: string | ArrayBuffer) {
}
}

View File

@ -10,15 +10,10 @@ import { JsonHubProtocol } from "./JsonHubProtocol";
import { NullLogger } from "./Loggers";
import { IStreamResult } from "./Stream";
import { TextMessageFormat } from "./TextMessageFormat";
import { createLogger, Subject } from "./Utils";
import { Arg, createLogger, Subject } from "./Utils";
export { JsonHubProtocol };
export interface IHubConnectionOptions extends IHttpConnectionOptions {
protocol?: IHubProtocol;
timeoutInMilliseconds?: number;
}
const DEFAULT_TIMEOUT_IN_MS: number = 30 * 1000;
export class HubConnection {
@ -31,27 +26,22 @@ export class HubConnection {
private id: number;
private closedCallbacks: Array<(error?: Error) => void>;
private timeoutHandle: NodeJS.Timer;
private timeoutInMilliseconds: number;
private receivedHandshakeResponse: boolean;
constructor(url: string, options?: IHubConnectionOptions);
constructor(connection: IConnection, options?: IHubConnectionOptions);
constructor(urlOrConnection: string | IConnection, options: IHubConnectionOptions = {}) {
options = options || {};
public serverTimeoutInMilliseconds: number;
this.timeoutInMilliseconds = options.timeoutInMilliseconds || DEFAULT_TIMEOUT_IN_MS;
constructor(connection: IConnection, logger: ILogger, protocol: IHubProtocol) {
Arg.isRequired(connection, "connection");
Arg.isRequired(logger, "logger");
Arg.isRequired(protocol, "protocol");
this.protocol = options.protocol || new JsonHubProtocol();
this.serverTimeoutInMilliseconds = DEFAULT_TIMEOUT_IN_MS;
this.logger = logger;
this.protocol = protocol;
this.connection = connection;
this.handshakeProtocol = new HandshakeProtocol();
if (typeof urlOrConnection === "string") {
this.connection = new HttpConnection(urlOrConnection, options);
} else {
this.connection = urlOrConnection;
}
this.logger = createLogger(options.logger);
this.connection.onreceive = (data: any) => this.processIncomingData(data);
this.connection.onclose = (error?: Error) => this.connectionClosed(error);
@ -293,7 +283,7 @@ export class HubConnection {
private configureTimeout() {
if (!this.connection.features || !this.connection.features.inherentKeepAlive) {
// Set the timeout timer
this.timeoutHandle = setTimeout(() => this.serverTimeout(), this.timeoutInMilliseconds);
this.timeoutHandle = setTimeout(() => this.serverTimeout(), this.serverTimeoutInMilliseconds);
}
}

View File

@ -0,0 +1,88 @@
// 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 { HttpConnection, IHttpConnectionOptions } from "./HttpConnection";
import { HubConnection, JsonHubProtocol } from "./HubConnection";
import { IHubProtocol } from "./IHubProtocol";
import { ILogger, LogLevel } from "./ILogger";
import { HttpTransportType } from "./ITransport";
import { NullLogger } from "./Loggers";
import { Arg, ConsoleLogger } from "./Utils";
export class HubConnectionBuilder {
/** @internal */
public protocol: IHubProtocol;
/** @internal */
public httpConnectionOptions: IHttpConnectionOptions;
/** @internal */
public url: string;
/** @internal */
public logger: ILogger;
public configureLogging(logging: LogLevel | ILogger): HubConnectionBuilder {
Arg.isRequired(logging, "logging");
if (isLogger(logging)) {
this.logger = logging;
} else {
this.logger = new ConsoleLogger(logging);
}
return this;
}
public withUrl(url: string): HubConnectionBuilder;
public withUrl(url: string, options: IHttpConnectionOptions): HubConnectionBuilder;
public withUrl(url: string, transportType: HttpTransportType): HubConnectionBuilder;
public withUrl(url: string, transportTypeOrOptions?: IHttpConnectionOptions | HttpTransportType): HubConnectionBuilder {
Arg.isRequired(url, "url");
this.url = url;
// Flow-typing knows where it's at. Since HttpTransportType is a number and IHttpConnectionOptions is guaranteed
// to be an object, we know (as does TypeScript) this comparison is all we need to figure out which overload was called.
if (typeof transportTypeOrOptions === "object") {
this.httpConnectionOptions = transportTypeOrOptions;
} else {
this.httpConnectionOptions = {
transport: transportTypeOrOptions,
};
}
return this;
}
public withHubProtocol(protocol: IHubProtocol): HubConnectionBuilder {
Arg.isRequired(protocol, "protocol");
this.protocol = protocol;
return this;
}
public build(): HubConnection {
// If httpConnectionOptions has a logger, use it. Otherwise, override it with the one
// provided to configureLogger
const httpConnectionOptions = this.httpConnectionOptions || {};
// If it's 'null', the user **explicitly** asked for null, don't mess with it.
if (httpConnectionOptions.logger === undefined) {
// If our logger is undefined or null, that's OK, the HttpConnection constructor will handle it.
httpConnectionOptions.logger = this.logger;
}
// Now create the connection
if (!this.url) {
throw new Error("The 'HubConnectionBuilder.withUrl' method must be called before building the connection.");
}
const connection = new HttpConnection(this.url, httpConnectionOptions);
return new HubConnection(
connection,
this.logger || NullLogger.instance,
this.protocol || new JsonHubProtocol());
}
}
function isLogger(logger: any): logger is ILogger {
return logger.log !== undefined;
}

View File

@ -164,11 +164,8 @@ export class ConsoleLogger implements ILogger {
case LogLevel.Information:
console.info(`${LogLevel[logLevel]}: ${message}`);
break;
case LogLevel.Trace:
case LogLevel.Debug:
console.debug(`${LogLevel[logLevel]}: ${message}`);
break;
default:
// console.debug only goes to attached debuggers in Node, so we use console.log for Trace and Debug
console.log(`${LogLevel[logLevel]}: ${message}`);
break;
}

View File

@ -6,6 +6,7 @@ export * from "./Errors";
export * from "./HttpClient";
export * from "./HttpConnection";
export * from "./HubConnection";
export * from "./HubConnectionBuilder";
export * from "./IConnection";
export * from "./IHubProtocol";
export * from "./ILogger";

View File

@ -13,6 +13,7 @@
"noImplicitAny": true,
"suppressImplicitAnyIndexErrors": true,
"noEmitOnError": true,
"stripInternal": true,
"lib": [ "es5", "es2015.promise", "es2015.iterable", "dom" ]
}
}

View File

@ -17,7 +17,10 @@
<script src="lib/signalr-client/signalr.js"></script>
<script>
let transportType = signalR.HttpTransportType[getParameterByName('transport')] || signalR.HttpTransportType.WebSockets;
let connection = new signalR.HubConnection('/chat', { transport: transportType, logger: signalR.LogLevel.Information });
let connection = new signalR.HubConnectionBuilder()
.configureLogging(signalR.LogLevel.Information)
.withUrl("/chat", transportType)
.build();
connection.onclose(e => {
if (e) {

View File

@ -70,8 +70,11 @@
transport: transportType,
accessTokenFactory: function () { return tokens[clientId]; }
};
connection = new signalR.HubConnectionBuilder()
.withUrl("/broadcast", options)
.configureLogging(signalR.LogLevel.Information)
.build();
connection = new signalR.HubConnection('/broadcast', options);
connection.on('Message', function (from, message) {
appendLog(clientId, from + ': ' + message);
});

View File

@ -139,14 +139,17 @@
new signalR.protocols.msgpack.MessagePackHubProtocol() :
new signalR.JsonHubProtocol();
let options = { logger: signalR.LogLevel.Trace, protocol: protocol };
var options = {};
if (transportDropdown.value !== "Automatic") {
options.transport = signalR.HttpTransportType[transportDropdown.value];
}
console.log('http://' + document.location.host + '/' + hubRoute);
connection = new signalR.HubConnection(hubRoute, options);
connection = new signalR.HubConnectionBuilder()
.configureLogging(signalR.LogLevel.Trace)
.withUrl(hubRoute, options)
.withHubProtocol(protocol)
.build();
connection.on('Send', function (msg) {
addLine('message-list', msg);
});

View File

@ -53,7 +53,10 @@
});
click('connectButton', function () {
connection = new signalR.HubConnection('/streaming', { transport: transportType, logger: signalR.LogLevel.Trace });
connection = new signalR.HubConnectionBuilder()
.configureLogging(signalR.LogLevel.Trace)
.withUrl("/streaming", transportType)
.build();
connection.onclose(function () {
channelButton.disabled = true;