diff --git a/client-ts/Microsoft.AspNetCore.Client.SignalR.TS.Tests/HubConnection.spec.ts b/client-ts/Microsoft.AspNetCore.Client.SignalR.TS.Tests/HubConnection.spec.ts new file mode 100644 index 0000000000..9d9c8fcae8 --- /dev/null +++ b/client-ts/Microsoft.AspNetCore.Client.SignalR.TS.Tests/HubConnection.spec.ts @@ -0,0 +1,75 @@ +import { IConnection } from "../Microsoft.AspNetCore.SignalR.Client.TS/IConnection" +import { HubConnection } from "../Microsoft.AspNetCore.SignalR.Client.TS/HubConnection" +import { DataReceived, ConnectionClosed } from "../Microsoft.AspNetCore.SignalR.Client.TS/Common" + +describe("HubConnection", () => { + it("completes pending invocations when stopped", async (done) => { + let connection: IConnection = { + start(transportName: string): Promise { + return Promise.resolve(); + }, + + send(data: any): Promise { + return Promise.resolve(); + }, + + stop(): void { + if (this.onClosed) { + this.onClosed(); + } + }, + + onDataReceived: null, + onClosed: null + }; + + let hubConnection = new HubConnection(connection); + var invokePromise = hubConnection.invoke("testMethod"); + hubConnection.stop(); + invokePromise + .then(() => { + fail(); + done(); + }) + .catch((error: Error) => { + expect(error.message).toBe("Invocation cancelled due to connection being closed."); + done(); + }); + }); + + it("completes pending invocations when connection is lost", async (done) => { + let connection: IConnection = { + start(transportName: string): Promise { + return Promise.resolve(); + }, + + send(data: any): Promise { + return Promise.resolve(); + }, + + stop(): void { + if (this.onClosed) { + this.onClosed(); + } + }, + + onDataReceived: null, + onClosed: null + }; + + let hubConnection = new HubConnection(connection); + var invokePromise = hubConnection.invoke("testMethod"); + invokePromise + .then(() => { + fail(); + done(); + }) + .catch((error: Error) => { + expect(error.message).toBe("Connection lost"); + done(); + }); + + // Typically this would be called by the transport + connection.onClosed(new Error("Connection lost")); + }); +}); \ No newline at end of file diff --git a/client-ts/Microsoft.AspNetCore.SignalR.Client.TS/HubConnection.ts b/client-ts/Microsoft.AspNetCore.SignalR.Client.TS/HubConnection.ts index 3dc7c481a1..7e9a03f739 100644 --- a/client-ts/Microsoft.AspNetCore.SignalR.Client.TS/HubConnection.ts +++ b/client-ts/Microsoft.AspNetCore.SignalR.Client.TS/HubConnection.ts @@ -21,6 +21,7 @@ export class HubConnection { private callbacks: Map void>; private methods: Map void>; private id: number; + private connectionClosedCallback: ConnectionClosed; static create(url: string, queryString?: string): HubConnection { return new this(new Connection(url, queryString)) @@ -33,6 +34,9 @@ export class HubConnection { this.connection.onDataReceived = data => { this.onDataReceived(data); }; + this.connection.onClosed = (error: Error) => { + this.onConnectionClosed(error); + } this.callbacks = new Map void>(); this.methods = new Map void>(); @@ -48,7 +52,7 @@ export class HubConnection { var descriptor = JSON.parse(data); if (descriptor.Method === undefined) { let invocationResult: InvocationResultDescriptor = descriptor; - let callback = this.callbacks[invocationResult.Id]; + let callback = this.callbacks.get(invocationResult.Id); if (callback != null) { callback(invocationResult); this.callbacks.delete(invocationResult.Id); @@ -64,6 +68,23 @@ export class HubConnection { } } + private onConnectionClosed(error: Error) { + let errorInvocationResult = { + Id: "-1", + Error: error ? error.message : "Invocation cancelled due to connection being closed.", + Result: null + } as InvocationResultDescriptor; + + this.callbacks.forEach(callback => { + callback(errorInvocationResult); + }); + this.callbacks.clear(); + + if (this.connectionClosedCallback) { + this.connectionClosedCallback(error); + } + } + start(transportName? :string): Promise { return this.connection.start(transportName); } @@ -83,20 +104,20 @@ export class HubConnection { }; let p = new Promise((resolve, reject) => { - this.callbacks[id] = (invocationResult: InvocationResultDescriptor) => { + this.callbacks.set(invocationDescriptor.Id, (invocationResult: InvocationResultDescriptor) => { if (invocationResult.Error != null) { reject(new Error(invocationResult.Error)); } else { resolve(invocationResult.Result); } - }; + }); //TODO: separate conversion to enable different data formats this.connection.send(JSON.stringify(invocationDescriptor)) .catch(e => { - // TODO: remove callback reject(e); + this.callbacks.delete(invocationDescriptor.Id); }); }); @@ -108,6 +129,6 @@ export class HubConnection { } set onClosed(callback: ConnectionClosed) { - this.connection.onClosed = callback; + this.connectionClosedCallback = callback; } }