diff --git a/packages/nocodb-nest/src/controllers/imports/import.controller.ts b/packages/nocodb-nest/src/controllers/imports/import.controller.ts index d20f74408f..11c998e44d 100644 --- a/packages/nocodb-nest/src/controllers/imports/import.controller.ts +++ b/packages/nocodb-nest/src/controllers/imports/import.controller.ts @@ -1,12 +1,12 @@ import { Controller, HttpCode, Post, Request, UseGuards } from '@nestjs/common'; import { forwardRef, Inject } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; +import { SocketGateway } from 'src/services/socket.gateway'; import { GlobalGuard } from '../../guards/global/global.guard'; import { NcError } from '../../helpers/catchError'; import { ExtractProjectIdMiddleware } from '../../middlewares/extract-project-id/extract-project-id.middleware'; import { SyncSource } from '../../models'; import NocoJobs from '../../jobs/NocoJobs'; -import { SocketService } from '../../services/socket.service'; import airtableSyncJob from './helpers/job'; import type { AirtableSyncConfig } from './helpers/job'; @@ -78,7 +78,7 @@ const initJob = (sv: Server, jobs: { [p: string]: { last_message: any } }) => { @UseGuards(ExtractProjectIdMiddleware, GlobalGuard) export class ImportController { constructor( - private readonly socketService: SocketService, + private readonly socketGateway: SocketGateway, @Inject(forwardRef(() => ModuleRef)) private readonly moduleRef: ModuleRef, ) {} @@ -95,7 +95,7 @@ export class ImportController { @Post('/api/v1/db/meta/syncs/:syncId/trigger') @HttpCode(200) async triggerSync(@Request() req) { - if (req.params.syncId in this.socketService.jobs) { + if (req.params.syncId in this.socketGateway.jobs) { NcError.badRequest('Sync already in progress'); } @@ -125,7 +125,7 @@ export class ImportController { }); }, 1000); - this.socketService.jobs[req.params.syncId] = { + this.socketGateway.jobs[req.params.syncId] = { last_message: { msg: 'Sync started', }, @@ -136,13 +136,13 @@ export class ImportController { @Post('/api/v1/db/meta/syncs/:syncId/abort') @HttpCode(200) async abortImport(@Request() req) { - if (req.params.syncId in this.socketService.jobs) { - delete this.socketService.jobs[req.params.syncId]; + if (req.params.syncId in this.socketGateway.jobs) { + delete this.socketGateway.jobs[req.params.syncId]; } return {}; } async onModuleInit() { - initJob(this.socketService.io, this.socketService.jobs); + initJob(this.socketGateway.io, this.socketGateway.jobs); } } diff --git a/packages/nocodb-nest/src/modules/global/global.module.ts b/packages/nocodb-nest/src/modules/global/global.module.ts index cbf9d84976..644f98d4f4 100644 --- a/packages/nocodb-nest/src/modules/global/global.module.ts +++ b/packages/nocodb-nest/src/modules/global/global.module.ts @@ -1,10 +1,10 @@ import { Global, Module } from '@nestjs/common'; import { JwtModule, JwtService } from '@nestjs/jwt'; import { ExtractJwt } from 'passport-jwt'; +import { SocketGateway } from 'src/services/socket.gateway'; import { Connection } from '../../connection/connection'; import { GlobalGuard } from '../../guards/global/global.guard'; import { MetaService } from '../../meta/meta.service'; -import { SocketService } from '../../services/socket.service'; import { JwtStrategy } from '../../strategies/jwt.strategy'; import NcConfigFactory from '../../utils/NcConfigFactory'; import { UsersService } from '../../services/users/users.service'; @@ -38,7 +38,7 @@ export const JwtStrategyProvider: Provider = { UsersService, JwtStrategyProvider, GlobalGuard, - SocketService, + SocketGateway, ], exports: [ Connection, @@ -46,7 +46,7 @@ export const JwtStrategyProvider: Provider = { JwtStrategyProvider, UsersService, GlobalGuard, - SocketService, + SocketGateway, ], }) export class GlobalModule {} diff --git a/packages/nocodb-nest/src/services/socket.gateway.ts b/packages/nocodb-nest/src/services/socket.gateway.ts new file mode 100644 index 0000000000..c621847de6 --- /dev/null +++ b/packages/nocodb-nest/src/services/socket.gateway.ts @@ -0,0 +1,79 @@ +import crypto from 'crypto'; +import { WebSocketGateway, WebSocketServer } from '@nestjs/websockets'; +import { Inject, Injectable } from '@nestjs/common'; +import { HttpAdapterHost } from '@nestjs/core'; +import { T } from 'nc-help'; +import { Server } from 'socket.io'; +import { AuthGuard } from '@nestjs/passport'; +import { ExecutionContextHost } from '@nestjs/core/helpers/execution-context-host'; +import { JwtStrategy } from '../strategies/jwt.strategy'; +import type { OnModuleInit } from '@nestjs/common'; +import type { Socket } from 'socket.io'; + +function getHash(str) { + return crypto.createHash('md5').update(str).digest('hex'); +} + +@WebSocketGateway({ + cors: { + origin: '*', + allowedHeaders: ['xc-auth'], + credentials: true, + }, +}) +@Injectable() +export class SocketGateway implements OnModuleInit { + // private server: HttpServer; + private clients: { [id: string]: Socket } = {}; + private _jobs: { [id: string]: { last_message: any } } = {}; + + constructor( + private jwtStrategy: JwtStrategy, + @Inject(HttpAdapterHost) private httpAdapterHost: HttpAdapterHost, + ) {} + + @WebSocketServer() + server: Server; + + async onModuleInit() { + this.server + .use(async (socket, next) => { + try { + const context = new ExecutionContextHost([socket.handshake as any]); + const guard = new (AuthGuard('jwt'))(context); + await guard.canActivate(context); + } catch {} + + next(); + }) + .on('connection', (socket) => { + this.clients[socket.id] = socket; + const id = getHash( + (process.env.NC_SERVER_UUID || T.id) + + (socket?.handshake as any)?.user?.id, + ); + + socket.on('page', (args) => { + T.page({ ...args, id }); + }); + socket.on('event', (args) => { + T.event({ ...args, id }); + }); + socket.on('subscribe', (room) => { + if (room in this.jobs) { + socket.join(room); + socket.emit('job'); + socket.emit('progress', this.jobs[room].last_message); + } + }); + }); + } + + public get io() { + return this.server; + } + + public get jobs() { + return this._jobs; + } +} diff --git a/packages/nocodb-nest/src/services/socket.service.ts b/packages/nocodb-nest/src/services/socket.service.ts index e45b9dbbc1..c621847de6 100644 --- a/packages/nocodb-nest/src/services/socket.service.ts +++ b/packages/nocodb-nest/src/services/socket.service.ts @@ -1,11 +1,11 @@ import crypto from 'crypto'; -import { Inject, Injectable, UnauthorizedException } from '@nestjs/common'; +import { WebSocketGateway, WebSocketServer } from '@nestjs/websockets'; +import { Inject, Injectable } from '@nestjs/common'; import { HttpAdapterHost } from '@nestjs/core'; import { T } from 'nc-help'; import { Server } from 'socket.io'; import { AuthGuard } from '@nestjs/passport'; import { ExecutionContextHost } from '@nestjs/core/helpers/execution-context-host'; -import Noco from '../Noco'; import { JwtStrategy } from '../strategies/jwt.strategy'; import type { OnModuleInit } from '@nestjs/common'; import type { Socket } from 'socket.io'; @@ -14,30 +14,29 @@ function getHash(str) { return crypto.createHash('md5').update(str).digest('hex'); } +@WebSocketGateway({ + cors: { + origin: '*', + allowedHeaders: ['xc-auth'], + credentials: true, + }, +}) @Injectable() -export class SocketService implements OnModuleInit { +export class SocketGateway implements OnModuleInit { // private server: HttpServer; private clients: { [id: string]: Socket } = {}; private _jobs: { [id: string]: { last_message: any } } = {}; - private _io: Server; constructor( private jwtStrategy: JwtStrategy, @Inject(HttpAdapterHost) private httpAdapterHost: HttpAdapterHost, ) {} + @WebSocketServer() + server: Server; + async onModuleInit() { - this._io = new Server( - Noco.httpServer ?? this.httpAdapterHost.httpAdapter.getHttpServer(), - { - cors: { - origin: '*', - allowedHeaders: ['xc-auth'], - credentials: true, - }, - }, - ); - this.io + this.server .use(async (socket, next) => { try { const context = new ExecutionContextHost([socket.handshake as any]); @@ -71,7 +70,7 @@ export class SocketService implements OnModuleInit { } public get io() { - return this._io; + return this.server; } public get jobs() {