From bee9fcb0d8e67cf567a81c80b90cff227521c033 Mon Sep 17 00:00:00 2001 From: Pawel Kadluczka Date: Mon, 18 Sep 2017 19:25:34 -0700 Subject: [PATCH] Adding support for multiple callbacks per client side method ...and a possibility to remove callbacks Fixes: #807 --- .../HubConnection.spec.ts | 144 +++++++++++++++++- .../HubConnection.ts | 37 ++++- 2 files changed, 174 insertions(+), 7 deletions(-) diff --git a/client-ts/Microsoft.AspNetCore.SignalR.Client.TS.Tests/HubConnection.spec.ts b/client-ts/Microsoft.AspNetCore.SignalR.Client.TS.Tests/HubConnection.spec.ts index c44a02ff5e..06be069cf1 100644 --- a/client-ts/Microsoft.AspNetCore.SignalR.Client.TS.Tests/HubConnection.spec.ts +++ b/client-ts/Microsoft.AspNetCore.SignalR.Client.TS.Tests/HubConnection.spec.ts @@ -7,14 +7,16 @@ import { DataReceived, ConnectionClosed } from "../Microsoft.AspNetCore.SignalR. import { TransportType, ITransport, TransferMode } from "../Microsoft.AspNetCore.SignalR.Client.TS/Transports" import { Observer } from "../Microsoft.AspNetCore.SignalR.Client.TS/Observable" import { TextMessageFormat } from "../Microsoft.AspNetCore.SignalR.Client.TS/Formatters" +import { ILogger, LogLevel } from "../Microsoft.AspNetCore.SignalR.Client.TS/ILogger" import { asyncit as it, captureException } from './JasmineUtils'; describe("HubConnection", () => { + describe("start", () => { it("sends negotiation message", async () => { let connection = new TestConnection(); - let hubConnection = new HubConnection(connection); + let hubConnection = new HubConnection(connection, { logging: null }); await hubConnection.start(); expect(connection.sentData.length).toBe(1) expect(JSON.parse(connection.sentData[0])).toEqual({ @@ -135,6 +137,146 @@ describe("HubConnection", () => { }); }); + describe("on", () => { + it("invocations ignored in callbacks not registered", async () => { + let warnings: string[] = []; + let logger = { + log: function(logLevel: LogLevel, message: string) { + if (logLevel === LogLevel.Warning) { + warnings.push(message); + } + } + }; + let connection = new TestConnection(); + let hubConnection = new HubConnection(connection, { logging: logger }); + + connection.receive({ + type: 1, + invocationId: 0, + target: "message", + arguments: ["test"], + nonblocking: true + }); + + expect(warnings).toEqual(["No client method with the name 'message' found."]); + }); + + it("callback invoked when servers invokes a method on the client", async () => { + let connection = new TestConnection(); + let hubConnection = new HubConnection(connection); + let value = 0; + hubConnection.on("message", v => value = v); + + connection.receive({ + type: 1, + invocationId: 0, + target: "message", + arguments: ["test"], + nonblocking: true + }); + + expect(value).toBe("test"); + }); + + it("can have multiple callbacks", async () => { + let connection = new TestConnection(); + let hubConnection = new HubConnection(connection); + let numInvocations1 = 0; + let numInvocations2 = 0; + hubConnection.on("message", () => numInvocations1++); + hubConnection.on("message", () => numInvocations2++); + + connection.receive({ + type: 1, + invocationId: 0, + target: "message", + arguments: [], + nonblocking: true + }); + + expect(numInvocations1).toBe(1); + expect(numInvocations2).toBe(1); + }); + + it("can unsubscribe from on", async () => { + let connection = new TestConnection(); + let hubConnection = new HubConnection(connection); + + var numInvocations = 0; + var callback = () => numInvocations++; + hubConnection.on("message", callback); + + connection.receive({ + type: 1, + invocationId: 0, + target: "message", + arguments: [], + nonblocking: true + }); + + hubConnection.off("message", callback); + + connection.receive({ + type: 1, + invocationId: 0, + target: "message", + arguments: [], + nonblocking: true + }); + + expect(numInvocations).toBe(1); + }); + + it("unsubscribing from non-existing callbacks no-ops", async () => { + let connection = new TestConnection(); + let hubConnection = new HubConnection(connection); + + hubConnection.off("_", () => {}); + hubConnection.on("message", t => {}); + hubConnection.on("message", () => {}); + }); + + it("using null/undefined for methodName or method no-ops", async () => { + let warnings: string[] = []; + let logger = { + log: function(logLevel: LogLevel, message: string) { + if (logLevel === LogLevel.Warning) { + warnings.push(message); + } + + } + }; + + let connection = new TestConnection(); + let hubConnection = new HubConnection(connection, { logging: logger }); + + hubConnection.on(null, undefined); + hubConnection.on(undefined, null); + hubConnection.on("message", null); + hubConnection.on("message", undefined); + hubConnection.on(null, () => {}); + hubConnection.on(undefined, () => {}); + + // invoke a method to make sure we are not trying to use null/undefined + connection.receive({ + type: 1, + invocationId: 0, + target: "message", + arguments: [], + nonblocking: true + }); + + expect(warnings).toEqual(["No client method with the name 'message' found."]); + + hubConnection.off(null, undefined); + hubConnection.off(undefined, null); + hubConnection.off("message", null); + hubConnection.off("message", undefined); + hubConnection.off(null, () => {}); + hubConnection.off(undefined, () => {}); + }); + }); + describe("stream", () => { it("sends an invocation", async () => { let connection = new TestConnection(); diff --git a/client-ts/Microsoft.AspNetCore.SignalR.Client.TS/HubConnection.ts b/client-ts/Microsoft.AspNetCore.SignalR.Client.TS/HubConnection.ts index 790cf57d02..f59bce05a2 100644 --- a/client-ts/Microsoft.AspNetCore.SignalR.Client.TS/HubConnection.ts +++ b/client-ts/Microsoft.AspNetCore.SignalR.Client.TS/HubConnection.ts @@ -25,7 +25,7 @@ export class HubConnection { private readonly logger: ILogger; private protocol: IHubProtocol; private callbacks: Map void>; - private methods: Map void>; + private methods: Map void)[]>; private id: number; private connectionClosedCallback: ConnectionClosed; @@ -49,7 +49,7 @@ export class HubConnection { } this.callbacks = new Map void>(); - this.methods = new Map void>(); + this.methods = new Map void)[]>(); this.id = 0; } @@ -82,9 +82,9 @@ export class HubConnection { } private invokeClientMethod(invocationMessage: InvocationMessage) { - let method = this.methods.get(invocationMessage.target.toLowerCase()); - if (method) { - method.apply(this, invocationMessage.arguments); + let methods = this.methods.get(invocationMessage.target.toLowerCase()); + if (methods) { + methods.forEach(m => m.apply(this, invocationMessage.arguments)); if (!invocationMessage.nonblocking) { // TODO: send result back to the server? } @@ -211,7 +211,32 @@ export class HubConnection { } on(methodName: string, method: (...args: any[]) => void) { - this.methods.set(methodName.toLowerCase(), method); + if (!methodName || !method) { + return; + } + + methodName = methodName.toLowerCase(); + if (!this.methods.has(methodName)) { + this.methods.set(methodName, []); + } + + this.methods.get(methodName).push(method); + } + + off(methodName: string, method: (...args: any[]) => void) { + if (!methodName || !method) { + return; + } + + methodName = methodName.toLowerCase(); + let handlers = this.methods.get(methodName); + if (!handlers) { + return; + } + var removeIdx = handlers.indexOf(method); + if (removeIdx != -1) { + handlers.splice(removeIdx, 1); + } } set onClosed(callback: ConnectionClosed) {