Decouple IConnection.ts and HubConnection.ts from http (#566)

* Decouple IConnection.ts and HubConnection.ts from http
- Removed TransportType from start
- Renamed Connection.ts to HttpConnection.ts
This commit is contained in:
David Fowler 2017-06-16 06:54:55 +02:00 committed by GitHub
parent 0298868c00
commit d169b96d2d
12 changed files with 84 additions and 81 deletions

View File

@ -1,6 +1,6 @@
import { IHttpClient } from "../Microsoft.AspNetCore.SignalR.Client.TS/HttpClient" import { IHttpClient } from "../Microsoft.AspNetCore.SignalR.Client.TS/HttpClient"
import { Connection } from "../Microsoft.AspNetCore.SignalR.Client.TS/Connection" import { HttpConnection } from "../Microsoft.AspNetCore.SignalR.Client.TS/HttpConnection"
import { ISignalROptions } from "../Microsoft.AspNetCore.SignalR.Client.TS/ISignalROptions" import { IHttpConnectionOptions } from "../Microsoft.AspNetCore.SignalR.Client.TS/IHttpConnectionOptions"
import { DataReceived, TransportClosed } from "../Microsoft.AspNetCore.SignalR.Client.TS/Common" import { DataReceived, TransportClosed } from "../Microsoft.AspNetCore.SignalR.Client.TS/Common"
import { ITransport, TransportType } from "../Microsoft.AspNetCore.SignalR.Client.TS/Transports" import { ITransport, TransportType } from "../Microsoft.AspNetCore.SignalR.Client.TS/Transports"
import { eachTransport } from "./Common"; import { eachTransport } from "./Common";
@ -8,7 +8,7 @@ import { eachTransport } from "./Common";
describe("Connection", () => { describe("Connection", () => {
it("starting connection fails if getting id fails", async (done) => { it("starting connection fails if getting id fails", async (done) => {
let options: ISignalROptions = { let options: IHttpConnectionOptions = {
httpClient: <IHttpClient>{ httpClient: <IHttpClient>{
options(url: string): Promise<string> { options(url: string): Promise<string> {
return Promise.reject("error"); return Promise.reject("error");
@ -17,9 +17,9 @@ describe("Connection", () => {
return Promise.resolve(""); return Promise.resolve("");
} }
} }
} as ISignalROptions; } as IHttpConnectionOptions;
let connection = new Connection("http://tempuri.org", options); let connection = new HttpConnection("http://tempuri.org", options);
try { try {
await connection.start(); await connection.start();
@ -33,28 +33,28 @@ describe("Connection", () => {
}); });
it("cannot start a running connection", async (done) => { it("cannot start a running connection", async (done) => {
let options: ISignalROptions = { let options: IHttpConnectionOptions = {
httpClient: <IHttpClient>{ httpClient: <IHttpClient>{
options(url: string): Promise<string> { options(url: string): Promise<string> {
connection.start() connection.start()
.then(() => { .then(() => {
fail(); fail();
done(); done();
}) })
.catch((error: Error) => { .catch((error: Error) => {
expect(error.message).toBe("Cannot start a connection that is not in the 'Initial' state."); expect(error.message).toBe("Cannot start a connection that is not in the 'Initial' state.");
done(); done();
}); });
return Promise.reject("error"); return Promise.reject("error");
}, },
get(url: string): Promise<string> { get(url: string): Promise<string> {
return Promise.resolve(""); return Promise.resolve("");
} }
} }
} as ISignalROptions; } as IHttpConnectionOptions;
let connection = new Connection("http://tempuri.org", options); let connection = new HttpConnection("http://tempuri.org", options);
try { try {
await connection.start(); await connection.start();
@ -66,7 +66,7 @@ describe("Connection", () => {
}); });
it("cannot start a stopped connection", async (done) => { it("cannot start a stopped connection", async (done) => {
let options: ISignalROptions = { let options: IHttpConnectionOptions = {
httpClient: <IHttpClient>{ httpClient: <IHttpClient>{
options(url: string): Promise<string> { options(url: string): Promise<string> {
return Promise.reject("error"); return Promise.reject("error");
@ -75,9 +75,9 @@ describe("Connection", () => {
return Promise.resolve(""); return Promise.resolve("");
} }
} }
} as ISignalROptions; } as IHttpConnectionOptions;
let connection = new Connection("http://tempuri.org", options); let connection = new HttpConnection("http://tempuri.org", options);
try { try {
// start will fail and transition the connection to the Disconnected state // start will fail and transition the connection to the Disconnected state
@ -99,7 +99,7 @@ describe("Connection", () => {
}); });
it("can stop a starting connection", async (done) => { it("can stop a starting connection", async (done) => {
let options: ISignalROptions = { let options: IHttpConnectionOptions = {
httpClient: <IHttpClient>{ httpClient: <IHttpClient>{
options(url: string): Promise<string> { options(url: string): Promise<string> {
connection.stop(); connection.stop();
@ -110,9 +110,9 @@ describe("Connection", () => {
return Promise.resolve(""); return Promise.resolve("");
} }
} }
} as ISignalROptions; } as IHttpConnectionOptions;
var connection = new Connection("http://tempuri.org", options); var connection = new HttpConnection("http://tempuri.org", options);
try { try {
await connection.start(); await connection.start();
@ -125,23 +125,12 @@ describe("Connection", () => {
}); });
it("can stop a non-started connection", async (done) => { it("can stop a non-started connection", async (done) => {
var connection = new Connection("http://tempuri.org"); var connection = new HttpConnection("http://tempuri.org");
await connection.stop(); await connection.stop();
done(); done();
}); });
it("preserves users connection string", async done => { it("preserves users connection string", async done => {
let options: ISignalROptions = {
httpClient: <IHttpClient>{
options(url: string): Promise<string> {
return Promise.resolve("{ \"connectionId\": \"42\" }");
},
get(url: string): Promise<string> {
return Promise.resolve("");
}
}
} as ISignalROptions;
let connectUrl: string; let connectUrl: string;
let fakeTransport: ITransport = { let fakeTransport: ITransport = {
connect(url: string): Promise<void> { connect(url: string): Promise<void> {
@ -156,10 +145,23 @@ describe("Connection", () => {
onClosed: undefined onClosed: undefined
} }
var connection = new Connection("http://tempuri.org?q=myData", options); let options: IHttpConnectionOptions = {
httpClient: <IHttpClient>{
options(url: string): Promise<string> {
return Promise.resolve("{ \"connectionId\": \"42\" }");
},
get(url: string): Promise<string> {
return Promise.resolve("");
}
},
transport: fakeTransport
} as IHttpConnectionOptions;
var connection = new HttpConnection("http://tempuri.org?q=myData", options);
try { try {
await connection.start(fakeTransport); await connection.start();
fail(); fail();
done(); done();
} }
@ -172,7 +174,7 @@ describe("Connection", () => {
eachTransport((requestedTransport: TransportType) => { eachTransport((requestedTransport: TransportType) => {
it(`Connection cannot be started if requested ${TransportType[requestedTransport]} transport not available on server`, async done => { it(`Connection cannot be started if requested ${TransportType[requestedTransport]} transport not available on server`, async done => {
let options: ISignalROptions = { let options: IHttpConnectionOptions = {
httpClient: <IHttpClient>{ httpClient: <IHttpClient>{
options(url: string): Promise<string> { options(url: string): Promise<string> {
return Promise.resolve("{ \"connectionId\": \"42\", \"availableTransports\": [] }"); return Promise.resolve("{ \"connectionId\": \"42\", \"availableTransports\": [] }");
@ -180,12 +182,13 @@ describe("Connection", () => {
get(url: string): Promise<string> { get(url: string): Promise<string> {
return Promise.resolve(""); return Promise.resolve("");
} }
} },
} as ISignalROptions; transport: requestedTransport
} as IHttpConnectionOptions;
var connection = new Connection("http://tempuri.org", options); var connection = new HttpConnection("http://tempuri.org", options);
try { try {
await connection.start(requestedTransport); await connection.start();
fail(); fail();
done(); done();
} }
@ -197,7 +200,7 @@ describe("Connection", () => {
}); });
it(`Connection cannot be started if no transport available on server and no transport requested`, async done => { it(`Connection cannot be started if no transport available on server and no transport requested`, async done => {
let options: ISignalROptions = { let options: IHttpConnectionOptions = {
httpClient: <IHttpClient>{ httpClient: <IHttpClient>{
options(url: string): Promise<string> { options(url: string): Promise<string> {
return Promise.resolve("{ \"connectionId\": \"42\", \"availableTransports\": [] }"); return Promise.resolve("{ \"connectionId\": \"42\", \"availableTransports\": [] }");
@ -206,9 +209,9 @@ describe("Connection", () => {
return Promise.resolve(""); return Promise.resolve("");
} }
} }
} as ISignalROptions; } as IHttpConnectionOptions;
var connection = new Connection("http://tempuri.org", options); var connection = new HttpConnection("http://tempuri.org", options);
try { try {
await connection.start(); await connection.start();
fail(); fail();

View File

@ -211,7 +211,7 @@ describe("HubConnection", () => {
}); });
class TestConnection implements IConnection { class TestConnection implements IConnection {
start(transportType: TransportType | ITransport): Promise<void> { start(): Promise<void> {
return Promise.resolve(); return Promise.resolve();
}; };

View File

@ -2,7 +2,7 @@ import { DataReceived, ConnectionClosed } from "./Common"
import { IConnection } from "./IConnection" import { IConnection } from "./IConnection"
import { ITransport, TransportType, WebSocketTransport, ServerSentEventsTransport, LongPollingTransport } from "./Transports" import { ITransport, TransportType, WebSocketTransport, ServerSentEventsTransport, LongPollingTransport } from "./Transports"
import { IHttpClient, HttpClient } from "./HttpClient" import { IHttpClient, HttpClient } from "./HttpClient"
import { ISignalROptions } from "./ISignalROptions" import { IHttpConnectionOptions } from "./IHttpConnectionOptions"
enum ConnectionState { enum ConnectionState {
Initial, Initial,
@ -16,32 +16,34 @@ interface INegotiateResponse {
availableTransports: string[] availableTransports: string[]
} }
export class Connection implements IConnection { export class HttpConnection implements IConnection {
private connectionState: ConnectionState; private connectionState: ConnectionState;
private url: string; private url: string;
private connectionId: string; private connectionId: string;
private httpClient: IHttpClient; private httpClient: IHttpClient;
private transport: ITransport; private transport: ITransport;
private options: IHttpConnectionOptions;
private startPromise: Promise<void>; private startPromise: Promise<void>;
constructor(url: string, options: ISignalROptions = {}) { constructor(url: string, options: IHttpConnectionOptions = {}) {
this.url = url; this.url = url;
this.httpClient = options.httpClient || new HttpClient(); this.httpClient = options.httpClient || new HttpClient();
this.connectionState = ConnectionState.Initial; this.connectionState = ConnectionState.Initial;
this.options = options;
} }
async start(transport?: TransportType | ITransport): Promise<void> { async start(): Promise<void> {
if (this.connectionState != ConnectionState.Initial) { if (this.connectionState != ConnectionState.Initial) {
return Promise.reject(new Error("Cannot start a connection that is not in the 'Initial' state.")); return Promise.reject(new Error("Cannot start a connection that is not in the 'Initial' state."));
} }
this.connectionState = ConnectionState.Connecting; this.connectionState = ConnectionState.Connecting;
this.startPromise = this.startInternal(transport); this.startPromise = this.startInternal();
return this.startPromise; return this.startPromise;
} }
private async startInternal(transportType: TransportType | ITransport): Promise<void> { private async startInternal(): Promise<void> {
try { try {
let negotiatePayload = await this.httpClient.options(this.url); let negotiatePayload = await this.httpClient.options(this.url);
let negotiateResponse: INegotiateResponse = JSON.parse(negotiatePayload); let negotiateResponse: INegotiateResponse = JSON.parse(negotiatePayload);
@ -54,7 +56,7 @@ export class Connection implements IConnection {
this.url += (this.url.indexOf("?") == -1 ? "?" : "&") + `id=${this.connectionId}`; this.url += (this.url.indexOf("?") == -1 ? "?" : "&") + `id=${this.connectionId}`;
this.transport = this.createTransport(transportType, negotiateResponse.availableTransports); this.transport = this.createTransport(this.options.transport, negotiateResponse.availableTransports);
this.transport.onDataReceived = this.onDataReceived; this.transport.onDataReceived = this.onDataReceived;
this.transport.onClosed = e => this.stopConnection(true, e); this.transport.onClosed = e => this.stopConnection(true, e);
await this.transport.connect(this.url); await this.transport.connect(this.url);

View File

@ -1,10 +1,9 @@
import { ConnectionClosed } from "./Common" import { ConnectionClosed } from "./Common"
import { IConnection } from "./IConnection" import { IConnection } from "./IConnection"
import { Connection } from "./Connection"
import { TransportType } from "./Transports" import { TransportType } from "./Transports"
import { Subject, Observable } from "./Observable" import { Subject, Observable } from "./Observable"
export { Connection } from "./Connection"
export { TransportType } from "./Transports" export { TransportType } from "./Transports"
export { HttpConnection } from "./HttpConnection"
import { IHubProtocol, MessageType, HubMessage, CompletionMessage, ResultMessage, InvocationMessage } from "./IHubProtocol"; import { IHubProtocol, MessageType, HubMessage, CompletionMessage, ResultMessage, InvocationMessage } from "./IHubProtocol";
import { JsonHubProtocol } from "./JsonHubProtocol"; import { JsonHubProtocol } from "./JsonHubProtocol";
@ -16,14 +15,8 @@ export class HubConnection {
private connectionClosedCallback: ConnectionClosed; private connectionClosedCallback: ConnectionClosed;
private protocol: IHubProtocol; private protocol: IHubProtocol;
static create(url: string, queryString?: string): HubConnection { constructor(connection: IConnection) {
return new this(new Connection(url, queryString)) this.connection = connection;
}
constructor(connection: IConnection);
constructor(url: string, queryString?: string);
constructor(connectionOrUrl: IConnection | string, queryString?: string) {
this.connection = typeof connectionOrUrl === "string" ? new Connection(connectionOrUrl, queryString) : connectionOrUrl;
this.connection.onDataReceived = data => { this.connection.onDataReceived = data => {
this.onDataReceived(data); this.onDataReceived(data);
}; };
@ -96,8 +89,8 @@ export class HubConnection {
} }
} }
start(transportType?: TransportType): Promise<void> { start(): Promise<void> {
return this.connection.start(transportType); return this.connection.start();
} }
stop(): void { stop(): void {

View File

@ -2,7 +2,7 @@ import { DataReceived, ConnectionClosed } from "./Common"
import { TransportType, ITransport } from "./Transports" import { TransportType, ITransport } from "./Transports"
export interface IConnection { export interface IConnection {
start(transportType: TransportType | ITransport): Promise<void>; start(): Promise<void>;
send(data: any): Promise<void>; send(data: any): Promise<void>;
stop(): void; stop(): void;

View File

@ -0,0 +1,7 @@
import { IHttpClient } from "./HttpClient"
import { TransportType, ITransport } from "./Transports"
export interface IHttpConnectionOptions {
httpClient?: IHttpClient;
transport?: TransportType | ITransport;
}

View File

@ -1,5 +0,0 @@
import {IHttpClient} from "./HttpClient"
export interface ISignalROptions {
httpClient?: IHttpClient;
}

View File

@ -1,5 +1,5 @@
export * from "./Common" export * from "./Common"
export * from "./Connection" export * from "./HttpConnection"
export * from "./HttpClient" export * from "./HttpClient"
export * from "./HubConnection" export * from "./HubConnection"
export * from "./Transports" export * from "./Transports"

View File

@ -17,7 +17,8 @@
<script src="lib/signalr-client/signalr-client.js"></script> <script src="lib/signalr-client/signalr-client.js"></script>
<script> <script>
let transportType = signalR.TransportType[getParameterByName('transport')] || signalR.TransportType.WebSockets; let transportType = signalR.TransportType[getParameterByName('transport')] || signalR.TransportType.WebSockets;
let connection = new signalR.HubConnection(`http://${document.location.host}/chat`, 'formatType=json&format=text'); let http = new signalR.HttpConnection(`http://${document.location.host}/chat`, { transport: transportType });
let connection = new signalR.HubConnection(http);
connection.onClosed = e => { connection.onClosed = e => {
if (e) { if (e) {
@ -59,7 +60,7 @@ connection.on('Send', (userName, message) => {
document.getElementById('messages').appendChild(child); document.getElementById('messages').appendChild(child);
}); });
connection.start(transportType).catch(err => appendLine(err, 'red')); connection.start().catch(err => appendLine(err, 'red'));
document.getElementById('sendmessage').addEventListener('submit', event => { document.getElementById('sendmessage').addEventListener('submit', event => {
let data = document.getElementById('new-message').value; let data = document.getElementById('new-message').value;

View File

@ -81,7 +81,8 @@ let transportType = signalR.TransportType[getParameterByName('transport')] || si
document.getElementById('head1').innerHTML = signalR.TransportType[transportType]; document.getElementById('head1').innerHTML = signalR.TransportType[transportType];
let connection = new signalR.HubConnection(`http://${document.location.host}/hubs`, 'formatType=json&format=text'); let http = new signalR.HttpConnection(`http://${document.location.host}/hubs`, { transport: transportType });
let connection = new signalR.HubConnection(http);
connection.on('Send', msg => { connection.on('Send', msg => {
addLine('message-list', msg); addLine('message-list', msg);
}); });
@ -96,7 +97,7 @@ connection.onClosed = e => {
} }
click('connect', event => { click('connect', event => {
connection.start(transportType) connection.start()
.then(() => { .then(() => {
isConnected = true; isConnected = true;
addLine('message-list', 'Connected successfully', 'green'); addLine('message-list', 'Connected successfully', 'green');

View File

@ -22,7 +22,7 @@
document.getElementById('transportName').innerHTML = signalR.TransportType[transportType]; document.getElementById('transportName').innerHTML = signalR.TransportType[transportType];
let url = `http://${document.location.host}/chat` let url = `http://${document.location.host}/chat`
let connection = new signalR.Connection(url); let connection = new signalR.HttpConnection(url, { transport: transportType });
connection.onDataReceived = data => { connection.onDataReceived = data => {
let child = document.createElement('li'); let child = document.createElement('li');
@ -36,7 +36,7 @@
event.preventDefault(); event.preventDefault();
}); });
connection.start(transportType).then(() => { connection.start().then(() => {
console.log("Opened"); console.log("Opened");
}, () => { }, () => {
console.log("Error opening connection"); console.log("Error opening connection");

View File

@ -1,4 +1,4 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
@ -53,7 +53,8 @@
}); });
click('connectButton', function () { click('connectButton', function () {
connection = new signalR.HubConnection(url, 'formatType=json&format=text'); let http = new signalR.HttpConnection(url, { transport: transportType });
connection = new signalR.HubConnection(http);
connection.onClosed = function () { connection.onClosed = function () {
channelButton.disabled = true; channelButton.disabled = true;
@ -64,7 +65,7 @@
addLine('resultsList', 'disconnected', 'green'); addLine('resultsList', 'disconnected', 'green');
}; };
connection.start(transportType) connection.start()
.then(function () { .then(function () {
channelButton.disabled = false; channelButton.disabled = false;
observableButton.disabled = false; observableButton.disabled = false;