From 6d9121263b11edb3f9cbb993caf6d963725b2cf8 Mon Sep 17 00:00:00 2001 From: Marius Friess <34072851+mariusfriess@users.noreply.github.com> Date: Fri, 4 Aug 2023 18:52:30 +0200 Subject: [PATCH] Handle closing rooms (#33) * close open channel if room is deleted * use events * format code * fix * fix delete & add option to close channel * remove obsolete code * Refactor code --------- Co-authored-by: Florian Raith <florianraith00@gmail.com> --- package-lock.json | 20 ++++++++++++- package.json | 1 + src/app.module.ts | 2 ++ src/channel/channel.gateway.ts | 18 ++++++++++++ src/channel/channel.service.ts | 51 ++++++++++++++++++++++++++-------- src/room/room.service.ts | 10 ++++++- 6 files changed, 88 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2376428..746ab95 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "@nestjs/common": "^9.4.2", "@nestjs/config": "^2.3.2", "@nestjs/core": "^9.4.2", + "@nestjs/event-emitter": "^2.0.0", "@nestjs/jwt": "^10.0.3", "@nestjs/passport": "^9.0.3", "@nestjs/platform-express": "^9.4.2", @@ -1855,6 +1856,19 @@ } } }, + "node_modules/@nestjs/event-emitter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@nestjs/event-emitter/-/event-emitter-2.0.0.tgz", + "integrity": "sha512-fZRv3+PmqXcbqCDRXRWhKDa+v3gmPUq4x5sQE5reVlDtEaCoAXwtGrtNswPtqd0msjyo8OWZF9k1sFjeRL6Xag==", + "dependencies": { + "eventemitter2": "6.4.9" + }, + "peerDependencies": { + "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", + "@nestjs/core": "^8.0.0 || ^9.0.0 || ^10.0.0", + "reflect-metadata": "^0.1.12" + } + }, "node_modules/@nestjs/jwt": { "version": "10.0.3", "resolved": "https://registry.npmjs.org/@nestjs/jwt/-/jwt-10.0.3.tgz", @@ -4715,6 +4729,11 @@ "node": ">= 0.6" } }, + "node_modules/eventemitter2": { + "version": "6.4.9", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz", + "integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==" + }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -9964,7 +9983,6 @@ "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, "engines": { "node": ">=0.10.0" } diff --git a/package.json b/package.json index 4053cd4..c4f79b5 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "@nestjs/common": "^9.4.2", "@nestjs/config": "^2.3.2", "@nestjs/core": "^9.4.2", + "@nestjs/event-emitter": "^2.0.0", "@nestjs/jwt": "^10.0.3", "@nestjs/passport": "^9.0.3", "@nestjs/platform-express": "^9.4.2", diff --git a/src/app.module.ts b/src/app.module.ts index 76bd509..2cd2707 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -9,6 +9,7 @@ import { RoomModule } from './room/room.module'; import { ExistsConstraint } from './common/constraints/exists'; import { ChannelModule } from './channel/channel.module'; import { BrowserModule } from './browser/browser.module'; +import { EventEmitterModule } from '@nestjs/event-emitter'; /** * Main application module where other modules are imported and configured. @@ -17,6 +18,7 @@ import { BrowserModule } from './browser/browser.module'; imports: [ MikroOrmModule.forRoot(), ConfigModule.forRoot(), + EventEmitterModule.forRoot(), AuthModule, UserModule, CategoryModule, diff --git a/src/channel/channel.gateway.ts b/src/channel/channel.gateway.ts index 3f24eda..6e1a2a2 100644 --- a/src/channel/channel.gateway.ts +++ b/src/channel/channel.gateway.ts @@ -302,6 +302,24 @@ export class ChannelGateway implements OnGatewayConnection { return true; } + /** + * Handle an 'close-channel' event to close a channel. + * @param client The connected socket client. + * @param payload The payload containing channelId. + * @returns A boolean indicating success. + */ + @SubscribeMessage('close-channel') + @UseRequestContext() + public async closeChannel( + @ConnectedSocket() client: Socket, + @MessageBody() payload: { channelId: string }, + ) { + const channel = await this.channels.fromId(payload.channelId); + await this.channels.close(channel); + + return true; + } + /** * Handle the connection event. * diff --git a/src/channel/channel.service.ts b/src/channel/channel.service.ts index ed212a4..387e029 100644 --- a/src/channel/channel.service.ts +++ b/src/channel/channel.service.ts @@ -6,6 +6,7 @@ import { Channel } from './channel'; import { RoomService } from '../room/room.service'; import { Room } from '../room/room.entity'; import { BrowserService } from '../browser/browser.service'; +import { OnEvent } from '@nestjs/event-emitter'; /** * Service for managing channels and real-time communication. @@ -158,24 +159,50 @@ export class ChannelService { this.logger.debug(`Left ${channel} as student ${client.id}`); } - /* - * If the channel is empty after the client left, close it after 1 second. - * This is a temporary solution to prevent the channel from being - * closed when the teacher leaves and rejoins. - * - * TODO: implement a better solution - */ if (channel?.isEmpty()) { channel.clearCloseTimeout(); - channel.setCloseTimeout(async () => { - delete this.channels[channelId]; - await this.rooms.updateWhiteboard(channel.room.id, channel.canvasJSON); - await this.browsers.closeBrowserContext(channelId); - this.logger.debug(`Closed ${channel}`); + channel.setCloseTimeout(() => { + this.close(channel); }); } } + public async close(channel: Channel) { + await this.rooms.updateWhiteboard(channel.room.id, channel.canvasJSON); + await this.browsers.closeBrowserContext(channel.id); + + await channel.close(); + delete this.channels[channel.id]; + this.logger.debug(`Closed ${channel}`); + } + + /** + * Event listener for when a room is deleted. Closes the associated channel, if it exists. + * + * @param room - The room entity that was deleted. + */ + @OnEvent('room.deleted') + public async onRoomDeleted(room: Room) { + const channel = this.getChannelFromRoom(room); + + if (channel) { + await this.close(channel); + } + } + + /** + * Gets the channel associated with the given id. + * + * @param id - The ID of the channel. + */ + public fromId(id: string): Channel { + if (!this.exists(id)) { + throw new WsException('Channel not found'); + } + + return this.channels[id]; + } + /** * Gets a channel associated with the given socket client. * diff --git a/src/room/room.service.ts b/src/room/room.service.ts index d4169fb..1c5f4ae 100644 --- a/src/room/room.service.ts +++ b/src/room/room.service.ts @@ -5,6 +5,7 @@ import { Room } from './room.entity'; import { EntityRepository } from '@mikro-orm/mysql'; import { Category } from '../category/category.entity'; import { Note } from '../note/note.entity'; +import { EventEmitter2 } from '@nestjs/event-emitter'; /** * Service responsible for managing room entities. @@ -15,6 +16,7 @@ export class RoomService { private readonly em: EntityManager, @InjectRepository(Room) private readonly repository: EntityRepository<Room>, + private eventEmitter: EventEmitter2, ) {} /** @@ -83,7 +85,13 @@ export class RoomService { public async delete(id: number, category: Category): Promise<void> { const room = await this.get(id, category); - await this.em.removeAndFlush(room); + if (!room) { + throw new Error('Room not found'); + } + + this.eventEmitter.emit('room.deleted', room); + + await this.repository.nativeDelete({ id }); } /** -- GitLab