feature: version one notification feature implemented.
This commit is contained in:
20
backend/src/realtime/realtime.emitter.ts
Normal file
20
backend/src/realtime/realtime.emitter.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Server } from 'socket.io';
|
||||
|
||||
export function userOrgRoom(userId: string, organizationId: string): string {
|
||||
return `user:${userId}:org:${organizationId}`;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class RealtimeEmitter {
|
||||
private server: Server | null = null;
|
||||
|
||||
setServer(server: Server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
emitToUserOrg(userId: string, organizationId: string, event: string, payload: unknown) {
|
||||
if (!this.server) return;
|
||||
this.server.to(userOrgRoom(userId, organizationId)).emit(event, payload);
|
||||
}
|
||||
}
|
||||
81
backend/src/realtime/realtime.gateway.ts
Normal file
81
backend/src/realtime/realtime.gateway.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import {
|
||||
OnGatewayConnection,
|
||||
OnGatewayInit,
|
||||
WebSocketGateway,
|
||||
WebSocketServer,
|
||||
} from '@nestjs/websockets';
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { Server, Socket } from 'socket.io';
|
||||
import { RealtimeEmitter, userOrgRoom } from './realtime.emitter';
|
||||
|
||||
type AccessPayload = {
|
||||
sub?: string;
|
||||
organizationId?: string;
|
||||
type?: string;
|
||||
};
|
||||
|
||||
@WebSocketGateway({
|
||||
namespace: '/realtime',
|
||||
cors: {
|
||||
origin: process.env.FRONTEND_URL || 'http://localhost:3001',
|
||||
credentials: true,
|
||||
},
|
||||
})
|
||||
export class RealtimeGateway implements OnGatewayInit, OnGatewayConnection {
|
||||
private readonly logger = new Logger(RealtimeGateway.name);
|
||||
|
||||
@WebSocketServer()
|
||||
server!: Server;
|
||||
|
||||
constructor(
|
||||
private readonly jwt: JwtService,
|
||||
private readonly config: ConfigService,
|
||||
private readonly emitter: RealtimeEmitter,
|
||||
) {}
|
||||
|
||||
afterInit(server: Server) {
|
||||
this.emitter.setServer(server);
|
||||
}
|
||||
|
||||
async handleConnection(client: Socket) {
|
||||
try {
|
||||
const token = this.readAccessToken(client);
|
||||
if (!token) {
|
||||
client.disconnect(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = await this.jwt.verifyAsync<AccessPayload>(token, {
|
||||
secret: this.config.get<string>('jwt.secret'),
|
||||
});
|
||||
|
||||
if (!payload?.sub || !payload.organizationId || payload.type !== 'access') {
|
||||
client.disconnect(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const room = userOrgRoom(payload.sub, payload.organizationId);
|
||||
await client.join(room);
|
||||
client.data.userId = payload.sub;
|
||||
client.data.organizationId = payload.organizationId;
|
||||
} catch (error) {
|
||||
this.logger.debug(`Realtime auth failed: ${String(error)}`);
|
||||
client.disconnect(true);
|
||||
}
|
||||
}
|
||||
|
||||
private readAccessToken(client: Socket): string | null {
|
||||
const cookieHeader = client.handshake.headers.cookie;
|
||||
if (!cookieHeader) return null;
|
||||
const parts = cookieHeader.split(';');
|
||||
for (const part of parts) {
|
||||
const [rawKey, ...rest] = part.trim().split('=');
|
||||
if (rawKey === 'accessToken') {
|
||||
return decodeURIComponent(rest.join('='));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
20
backend/src/realtime/realtime.module.ts
Normal file
20
backend/src/realtime/realtime.module.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { JwtModule } from '@nestjs/jwt';
|
||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
import { RealtimeEmitter } from './realtime.emitter';
|
||||
import { RealtimeGateway } from './realtime.gateway';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
JwtModule.registerAsync({
|
||||
imports: [ConfigModule],
|
||||
inject: [ConfigService],
|
||||
useFactory: (config: ConfigService) => ({
|
||||
secret: config.get<string>('jwt.secret'),
|
||||
}),
|
||||
}),
|
||||
],
|
||||
providers: [RealtimeEmitter, RealtimeGateway],
|
||||
exports: [RealtimeEmitter],
|
||||
})
|
||||
export class RealtimeModule {}
|
||||
Reference in New Issue
Block a user