websocket cursor data broadcast

This commit is contained in:
2025-11-29 22:33:31 +08:00
parent 978158353c
commit 5d7743c498
6 changed files with 342 additions and 6 deletions

View File

@@ -5,6 +5,7 @@ import { AppService } from './app.service';
import { UsersModule } from './users/users.module';
import { AuthModule } from './auth/auth.module';
import { DatabaseModule } from './database/database.module';
import { WsModule } from './ws/ws.module';
/**
* Validates required environment variables.
@@ -51,6 +52,7 @@ function validateEnvironment(config: Record<string, any>): Record<string, any> {
DatabaseModule, // Global database module for Prisma
UsersModule,
AuthModule,
WsModule,
],
controllers: [AppController],
providers: [AppService],

View File

@@ -0,0 +1,97 @@
import { Test, TestingModule } from '@nestjs/testing';
import { StateGateway } from './state.gateway';
import { Socket } from 'socket.io';
describe('StateGateway', () => {
let gateway: StateGateway;
let mockLoggerLog: jest.SpyInstance;
let mockLoggerDebug: jest.SpyInstance;
let mockServer: any;
beforeEach(async () => {
mockServer = {
sockets: {
sockets: {
size: 5,
},
},
};
const module: TestingModule = await Test.createTestingModule({
providers: [StateGateway],
}).compile();
gateway = module.get<StateGateway>(StateGateway);
gateway.io = mockServer;
// Spy on logger methods
mockLoggerLog = jest
// gateway is private in `state.gateway.ts` so we have to
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
.spyOn((gateway as any).logger, 'log')
.mockImplementation();
mockLoggerDebug = jest
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
.spyOn((gateway as any).logger, 'debug')
.mockImplementation();
});
afterEach(() => {
jest.restoreAllMocks();
});
it('should be defined', () => {
expect(gateway).toBeDefined();
});
describe('afterInit', () => {
it('should log initialization message', () => {
gateway.afterInit();
expect(mockLoggerLog).toHaveBeenCalledWith('Initialized');
});
});
describe('handleConnection', () => {
it('should log client connection and number of connected clients', () => {
const mockClient = { id: 'client1' } as Socket;
gateway.handleConnection(mockClient);
expect(mockLoggerLog).toHaveBeenCalledWith(
`Client id: ${mockClient.id} connected`,
);
expect(mockLoggerDebug).toHaveBeenCalledWith(
'Number of connected clients: 5',
);
});
});
describe('handleDisconnect', () => {
it('should log client disconnection', () => {
const mockClient = { id: 'client1' } as Socket;
gateway.handleDisconnect(mockClient);
expect(mockLoggerLog).toHaveBeenCalledWith(
`Cliend id:${mockClient.id} disconnected`,
);
});
});
describe('handleCursorReportPosition', () => {
it('should log message received from client', () => {
const mockClient = { id: 'client1' } as Socket;
const data = { x: 100, y: 200 };
gateway.handleCursorReportPosition(mockClient, data);
expect(mockLoggerLog).toHaveBeenCalledWith(
`Message received from client id: ${mockClient.id}`,
);
expect(mockLoggerDebug).toHaveBeenCalledWith(
`Payload: ${JSON.stringify(data, null, 0)}`,
);
});
});
});

View File

@@ -0,0 +1,41 @@
import { Logger } from '@nestjs/common';
import {
OnGatewayConnection,
OnGatewayDisconnect,
OnGatewayInit,
SubscribeMessage,
WebSocketGateway,
WebSocketServer,
} from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';
@WebSocketGateway()
export class StateGateway
implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect
{
private readonly logger = new Logger(StateGateway.name);
@WebSocketServer() io: Server;
afterInit() {
this.logger.log('Initialized');
}
handleConnection(client: Socket) {
const { sockets } = this.io.sockets;
this.logger.log(`Client id: ${client.id} connected`);
this.logger.debug(`Number of connected clients: ${sockets.size}`);
}
handleDisconnect(client: Socket) {
this.logger.log(`Cliend id:${client.id} disconnected`);
}
@SubscribeMessage('cursor-report-position')
handleCursorReportPosition(client: Socket, data: any) {
this.logger.log(`Message received from client id: ${client.id}`);
this.logger.debug(`Payload: ${JSON.stringify(data, null, 0)}`);
}
}

7
src/ws/ws.module.ts Normal file
View File

@@ -0,0 +1,7 @@
import { Module } from '@nestjs/common';
import { StateGateway } from './state/state.gateway';
@Module({
providers: [StateGateway],
})
export class WsModule {}