diff --git a/packages/third-parties/socketio/src/class/SocketHandlersBuilder.spec.ts b/packages/third-parties/socketio/src/class/SocketHandlersBuilder.spec.ts index 1fe23bfec92..2cc2e2d3c94 100644 --- a/packages/third-parties/socketio/src/class/SocketHandlersBuilder.spec.ts +++ b/packages/third-parties/socketio/src/class/SocketHandlersBuilder.spec.ts @@ -1,10 +1,11 @@ -import {PlatformTest} from "@tsed/common"; +import {DIContext, getContext, PlatformTest} from "@tsed/common"; import {Store} from "@tsed/core"; import {InjectorService, ProviderType} from "@tsed/di"; import {SocketFilters} from "../interfaces/SocketFilters"; import {SocketReturnsTypes} from "../interfaces/SocketReturnsTypes"; import {SocketHandlersBuilder} from "./SocketHandlersBuilder"; import {SocketProviderMetadata} from "./SocketProviderMetadata"; +import {expectation} from "sinon"; const metadata: any = { handlers: { @@ -38,6 +39,9 @@ describe("SocketHandlersBuilder", () => { }; const builder: any = new SocketHandlersBuilder(provider, { + alterAsync(_event, fn, _ctx) { + return fn; + }, get() { return instance; } @@ -114,7 +118,8 @@ describe("SocketHandlersBuilder", () => { injectNamespaces: [{nsp: "/nsp", propertyKey: "key"}], handlers: { $onConnection: { - eventName: "onConnection" + eventName: "connection", + methodClassName: "$onConnection" } as any } }) @@ -126,6 +131,9 @@ describe("SocketHandlersBuilder", () => { }; const builder: any = new SocketHandlersBuilder(provider, { + alterAsync(_event, fn, _ctx) { + return fn; + }, get() { return instance; } @@ -139,13 +147,57 @@ describe("SocketHandlersBuilder", () => { expect(buildHandlersStub).toBeCalledWith(socketStub, nspStub); expect(invokeStub).toBeCalledWith( instance, - {eventName: "onConnection"}, + {eventName: "connection", methodClassName: "$onConnection"}, { socket: socketStub, nsp: nspStub } ); }); + + it("should call the $onConnection in the context", async () => { + let ctx!: DIContext; + + const instance = { + $onConnection: jest.fn().mockImplementation(() => { + ctx = getContext(); + }) + }; + + const provider: any = { + store: { + get: jest.fn().mockReturnValue({ + injectNamespace: "nsp", + handlers: { + $onConnection: { + eventName: "connection", + methodClassName: "$onConnection" + } + } + }) + } + }; + const nspStub: any = {nsp: "nsp", name: "nsp"}; + const socketStub: any = { + id: "id", + on: jest.fn() + }; + + const builder: any = new SocketHandlersBuilder(provider, { + alterAsync(_event, fn, _ctx) { + return fn; + }, + get() { + return instance; + } + } as any); + + await builder.onConnection(socketStub, nspStub); + + expect(ctx).toMatchObject({ + id: expect.any(String) + }); + }); }); describe("onDisconnect()", () => { it("should create the $onDisconnect method if is missing", async () => { @@ -159,7 +211,8 @@ describe("SocketHandlersBuilder", () => { injectNamespace: "nsp", handlers: { $onDisconnect: { - eventName: "onDisconnect" + eventName: "disconnect", + methodClassName: "$onDisconnect" } } }) @@ -171,6 +224,9 @@ describe("SocketHandlersBuilder", () => { }; const builder: any = new SocketHandlersBuilder(provider, { + alterAsync(_event, fn, _ctx) { + return fn; + }, get() { return instance; } @@ -181,7 +237,7 @@ describe("SocketHandlersBuilder", () => { expect(invokeStub).toBeCalledWith( instance, - {eventName: "onDisconnect"}, + {eventName: "disconnect", methodClassName: "$onDisconnect"}, { socket: socketStub, nsp: nspStub @@ -200,7 +256,8 @@ describe("SocketHandlersBuilder", () => { injectNamespace: "nsp", handlers: { $onDisconnect: { - eventName: "onDisconnect" + eventName: "disconnect", + methodClassName: "$onDisconnect" } } }) @@ -213,6 +270,9 @@ describe("SocketHandlersBuilder", () => { }; const builder: any = new SocketHandlersBuilder(provider, { + alterAsync(_event, fn, _ctx) { + return fn; + }, get() { return instance; } @@ -223,7 +283,7 @@ describe("SocketHandlersBuilder", () => { expect(invokeStub).toBeCalledWith( instance, - {eventName: "onDisconnect"}, + {eventName: "disconnect", methodClassName: "$onDisconnect"}, { reason, socket: socketStub, @@ -231,6 +291,50 @@ describe("SocketHandlersBuilder", () => { } ); }); + + it("should call the $onDisconnect in the context", async () => { + let ctx!: DIContext; + + const instance = { + $onDisconnect: jest.fn().mockImplementation(() => { + ctx = getContext(); + }) + }; + + const provider: any = { + store: { + get: jest.fn().mockReturnValue({ + injectNamespace: "nsp", + handlers: { + $onDisconnect: { + eventName: "disconnect", + methodClassName: "$onDisconnect" + } + } + }) + } + }; + const nspStub: any = {nsp: "nsp", name: "nsp"}; + const socketStub: any = { + id: "id", + on: jest.fn() + }; + + const builder: any = new SocketHandlersBuilder(provider, { + alterAsync(_event, fn, _ctx) { + return fn; + }, + get() { + return instance; + } + } as any); + + await builder.onDisconnect(socketStub, nspStub); + + expect(ctx).toMatchObject({ + id: expect.any(String) + }); + }); }); describe("buildHandlers()", () => { @@ -248,17 +352,61 @@ describe("SocketHandlersBuilder", () => { } }; const socketStub = { - on: jest.fn() + on: jest.fn().mockImplementation((_, fn) => fn("arg1")) }; - const builder: any = new SocketHandlersBuilder(provider, {} as any); + const builder: any = new SocketHandlersBuilder(provider, { + alterAsync(_event, fn, _ctx) { + return fn; + } + } as any); jest.spyOn(builder, "runQueue").mockResolvedValue(undefined); await builder.buildHandlers(socketStub, "ws"); - socketStub.on.mock.calls[0][1]("arg1"); expect(socketStub.on).toBeCalledWith("eventName", expect.any(Function)); expect(builder.runQueue).toBeCalledWith(metadata.handlers.testHandler, ["arg1"], socketStub, "ws"); }); + + it("should call the method instance in the context", async () => { + const metadata = { + handlers: { + testHandler: { + eventName: "eventName", + methodClassName: "testHandler" + } + } + }; + let ctx!: DIContext; + const instance = { + testHandler: jest.fn().mockImplementation(() => { + ctx = getContext(); + }) + }; + const provider: any = { + store: { + get: jest.fn().mockReturnValue(metadata) + } + }; + let promise!: Promise; + const socketStub = { + on: jest.fn().mockImplementation((_, fn) => (promise = fn("arg1"))) + }; + const builder: any = new SocketHandlersBuilder(provider, { + alterAsync(_event, fn, _ctx) { + return fn; + }, + get() { + return instance; + } + } as any); + + await builder.buildHandlers(socketStub, "ws"); + await promise; + + expect(ctx).toMatchObject({ + id: expect.any(String) + }); + }); }); describe("invoke()", () => { it("should call the method instance", () => { @@ -272,6 +420,9 @@ describe("SocketHandlersBuilder", () => { }; const builder: any = new SocketHandlersBuilder(provider, { + alterAsync(_event, fn, _ctx) { + return fn; + }, get() { return instance; } @@ -299,6 +450,9 @@ describe("SocketHandlersBuilder", () => { }; const builder: any = new SocketHandlersBuilder(provider, { + alterAsync(_event, fn, _ctx) { + return fn; + }, get() { return instance; } diff --git a/packages/third-parties/socketio/src/class/SocketHandlersBuilder.ts b/packages/third-parties/socketio/src/class/SocketHandlersBuilder.ts index 3c40e125026..617ff36b666 100644 --- a/packages/third-parties/socketio/src/class/SocketHandlersBuilder.ts +++ b/packages/third-parties/socketio/src/class/SocketHandlersBuilder.ts @@ -1,5 +1,5 @@ import {isFunction, Store} from "@tsed/core"; -import {InjectorService, Provider} from "@tsed/di"; +import {DIContext, InjectorService, Provider, runInContext} from "@tsed/di"; import {deserialize} from "@tsed/json-mapper"; import {$log} from "@tsed/logger"; import {Namespace, Socket} from "socket.io"; @@ -11,6 +11,7 @@ import {SocketProviderTypes} from "../interfaces/SocketProviderTypes"; import {SocketReturnsTypes} from "../interfaces/SocketReturnsTypes"; import {SocketProviderMetadata} from "./SocketProviderMetadata"; import {SocketSessionData} from "./SocketSessionData"; +import {v4} from "uuid"; /** * @ignore @@ -18,7 +19,10 @@ import {SocketSessionData} from "./SocketSessionData"; export class SocketHandlersBuilder { private readonly socketProviderMetadata: SocketProviderMetadata; - constructor(private provider: Provider, private injector: InjectorService) { + constructor( + private readonly provider: Provider, + private readonly injector: InjectorService + ) { this.socketProviderMetadata = new SocketProviderMetadata(this.provider.store.get("socketIO")); } @@ -90,7 +94,8 @@ export class SocketHandlersBuilder { this.buildHandlers(socket, nsp); if (instance.$onConnection) { - await this.invoke(instance, socketProviderMetadata.$onConnection, {socket, nsp}); + const ctx = this.createContext(socket, nsp); + await runInContext(ctx, () => this.invoke(instance, socketProviderMetadata.$onConnection, {socket, nsp}), this.injector); } } @@ -99,7 +104,8 @@ export class SocketHandlersBuilder { const {socketProviderMetadata} = this; if (instance.$onDisconnect) { - await this.invoke(instance, socketProviderMetadata.$onDisconnect, {socket, nsp, reason}); + const ctx = this.createContext(socket, nsp); + await runInContext(ctx, () => this.invoke(instance, socketProviderMetadata.$onDisconnect, {socket, nsp, reason}), this.injector); } } @@ -110,8 +116,9 @@ export class SocketHandlersBuilder { const {eventName} = handler; if (eventName) { - socket.on(eventName, (...args) => { - this.runQueue(handler, args, socket, nsp); + socket.on(eventName, async (...args) => { + const ctx = this.createContext(socket, nsp); + await runInContext(ctx, () => this.runQueue(handler, args, socket, nsp), this.injector); }); } }); @@ -240,4 +247,17 @@ export class SocketHandlersBuilder { } }); } + + private createContext(socket: Socket, nsp: Namespace): DIContext { + return new DIContext({ + injector: this.injector, + id: v4().split("-").join(""), + logger: this.injector.logger, + additionalProps: { + module: "socket.io", + sid: socket.id, + namespace: nsp.name + } + }); + } }