Skip to content

Commit

Permalink
refactor(socketio): enhance context management for event listeners
Browse files Browse the repository at this point in the history
closes #2677
  • Loading branch information
derevnjuk committed Apr 25, 2024
1 parent 64181a1 commit 6369461
Show file tree
Hide file tree
Showing 2 changed files with 190 additions and 16 deletions.
174 changes: 164 additions & 10 deletions packages/third-parties/socketio/src/class/SocketHandlersBuilder.spec.ts
Original file line number Diff line number Diff line change
@@ -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: {
Expand Down Expand Up @@ -38,6 +39,9 @@ describe("SocketHandlersBuilder", () => {
};

const builder: any = new SocketHandlersBuilder(provider, {
alterAsync(_event, fn, _ctx) {
return fn;
},
get() {
return instance;
}
Expand Down Expand Up @@ -114,7 +118,8 @@ describe("SocketHandlersBuilder", () => {
injectNamespaces: [{nsp: "/nsp", propertyKey: "key"}],
handlers: {
$onConnection: {
eventName: "onConnection"
eventName: "connection",
methodClassName: "$onConnection"
} as any
}
})
Expand All @@ -126,6 +131,9 @@ describe("SocketHandlersBuilder", () => {
};

const builder: any = new SocketHandlersBuilder(provider, {
alterAsync(_event, fn, _ctx) {
return fn;
},
get() {
return instance;
}
Expand All @@ -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 () => {
Expand All @@ -159,7 +211,8 @@ describe("SocketHandlersBuilder", () => {
injectNamespace: "nsp",
handlers: {
$onDisconnect: {
eventName: "onDisconnect"
eventName: "disconnect",
methodClassName: "$onDisconnect"
}
}
})
Expand All @@ -171,6 +224,9 @@ describe("SocketHandlersBuilder", () => {
};

const builder: any = new SocketHandlersBuilder(provider, {
alterAsync(_event, fn, _ctx) {
return fn;
},
get() {
return instance;
}
Expand All @@ -181,7 +237,7 @@ describe("SocketHandlersBuilder", () => {

expect(invokeStub).toBeCalledWith(
instance,
{eventName: "onDisconnect"},
{eventName: "disconnect", methodClassName: "$onDisconnect"},
{
socket: socketStub,
nsp: nspStub
Expand All @@ -200,7 +256,8 @@ describe("SocketHandlersBuilder", () => {
injectNamespace: "nsp",
handlers: {
$onDisconnect: {
eventName: "onDisconnect"
eventName: "disconnect",
methodClassName: "$onDisconnect"
}
}
})
Expand All @@ -213,6 +270,9 @@ describe("SocketHandlersBuilder", () => {
};

const builder: any = new SocketHandlersBuilder(provider, {
alterAsync(_event, fn, _ctx) {
return fn;
},
get() {
return instance;
}
Expand All @@ -223,14 +283,58 @@ describe("SocketHandlersBuilder", () => {

expect(invokeStub).toBeCalledWith(
instance,
{eventName: "onDisconnect"},
{eventName: "disconnect", methodClassName: "$onDisconnect"},
{
reason,
socket: socketStub,
nsp: nspStub
}
);
});

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()", () => {
Expand All @@ -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<unknown>;
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", () => {
Expand All @@ -272,6 +420,9 @@ describe("SocketHandlersBuilder", () => {
};

const builder: any = new SocketHandlersBuilder(provider, {
alterAsync(_event, fn, _ctx) {
return fn;
},
get() {
return instance;
}
Expand Down Expand Up @@ -299,6 +450,9 @@ describe("SocketHandlersBuilder", () => {
};

const builder: any = new SocketHandlersBuilder(provider, {
alterAsync(_event, fn, _ctx) {
return fn;
},
get() {
return instance;
}
Expand Down
32 changes: 26 additions & 6 deletions packages/third-parties/socketio/src/class/SocketHandlersBuilder.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -11,14 +11,18 @@ import {SocketProviderTypes} from "../interfaces/SocketProviderTypes";
import {SocketReturnsTypes} from "../interfaces/SocketReturnsTypes";
import {SocketProviderMetadata} from "./SocketProviderMetadata";
import {SocketSessionData} from "./SocketSessionData";
import {v4} from "uuid";

/**
* @ignore
*/
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"));
}

Expand Down Expand Up @@ -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);
}
}

Expand All @@ -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);
}
}

Expand All @@ -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);
});
}
});
Expand Down Expand Up @@ -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
}
});
}
}

0 comments on commit 6369461

Please sign in to comment.