feat(service): P2.3 Socket.io /message gateway + real SessionVerifier

open_thread (create-or-join) / send_message / read / delivered / typing; HS256
token verified on connect (SessionVerifier reuses AppTokenVerifier pattern);
OutboxBus bridge propagates REST/ingest messages to rooms (dedup vs inline emit).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-01 01:12:49 +05:30
parent 827fb52f5f
commit 50ef34dd90
6 changed files with 375 additions and 5 deletions
+3
View File
@@ -16,7 +16,10 @@
"@nestjs/common": "^11.1.27",
"@nestjs/core": "^11.1.27",
"@nestjs/platform-express": "^11.1.27",
"@nestjs/platform-socket.io": "^11.1.27",
"@nestjs/websockets": "^11.1.27",
"@prisma/client": "^6.2.1",
"socket.io": "^4.8.3",
"class-transformer": "^0.5.1",
"class-validator": "^0.15.1",
"jsonwebtoken": "^9.0.3",
@@ -0,0 +1,111 @@
import { randomUUID } from 'node:crypto';
import { Logger } from '@nestjs/common';
import {
ConnectedSocket,
MessageBody,
OnGatewayConnection,
OnGatewayInit,
SubscribeMessage,
WebSocketGateway,
WebSocketServer,
} from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';
import { IIOS_EVENTS } from '@insignia/iios-contracts';
import { MessageService, type MessagePrincipal } from './message.service';
import { SessionVerifier } from '../platform/session.verifier';
import { OutboxBus } from '../outbox/outbox.bus';
interface SocketState {
principal: MessagePrincipal;
}
/**
* Realtime messaging over Socket.io `/message` (reuses the support-service
* gateway pattern). Token verified synchronously on connect. Send writes via
* MessageService and emits inline to the thread room; the OutboxBus bridge
* propagates messages persisted elsewhere (REST/ingest) to connected clients.
*/
@WebSocketGateway({ namespace: '/message', cors: { origin: true } })
export class MessageGateway implements OnGatewayInit, OnGatewayConnection {
private readonly logger = new Logger(MessageGateway.name);
private readonly emittedLocally = new Set<string>();
@WebSocketServer() server!: Server;
constructor(
private readonly messages: MessageService,
private readonly session: SessionVerifier,
private readonly bus: OutboxBus,
) {}
afterInit(): void {
this.bus.on(IIOS_EVENTS.messageSent, (payload) => {
const ce = payload as { data?: { interactionId?: string; threadId?: string } };
const interactionId = ce?.data?.interactionId;
const threadId = ce?.data?.threadId;
if (!interactionId || !threadId) return;
if (this.emittedLocally.delete(interactionId)) return; // already emitted inline
void this.messages.getMessageById(interactionId).then((m) => {
if (m) this.server.to(threadId).emit('message', m);
});
});
}
handleConnection(client: Socket): void {
try {
const token = String(client.handshake.auth?.token ?? '');
const principal = this.session.verify(token);
client.data = { principal } satisfies SocketState;
} catch (err) {
this.logger.warn(`rejecting socket: ${(err as Error).message}`);
client.disconnect(true);
}
}
@SubscribeMessage('open_thread')
async openThread(@ConnectedSocket() client: Socket, @MessageBody() body: { threadId?: string }) {
const { principal } = client.data as SocketState;
const result = await this.messages.openThread(body?.threadId ?? null, principal);
await client.join(result.threadId);
return result;
}
@SubscribeMessage('send_message')
async sendMessage(
@ConnectedSocket() client: Socket,
@MessageBody() body: { threadId: string; content: string; contentRef?: string },
) {
const { principal } = client.data as SocketState;
const msg = await this.messages.send(
body.threadId,
principal,
{ content: body.content, contentRef: body.contentRef },
randomUUID(),
);
this.emittedLocally.add(msg.id);
this.server.to(body.threadId).emit('message', msg);
return msg;
}
@SubscribeMessage('read')
async read(@ConnectedSocket() client: Socket, @MessageBody() body: { threadId: string; interactionId: string }) {
const { principal } = client.data as SocketState;
const r = await this.messages.markRead(body.threadId, principal, body.interactionId);
this.server.to(body.threadId).emit('receipt', { interactionId: r.interactionId, actorId: r.actorId, kind: 'READ' });
return { ok: true };
}
@SubscribeMessage('delivered')
async delivered(@ConnectedSocket() client: Socket, @MessageBody() body: { threadId: string; interactionId: string }) {
const { principal } = client.data as SocketState;
const r = await this.messages.markDelivered(body.threadId, principal, body.interactionId);
this.server.to(body.threadId).emit('receipt', { interactionId: r.interactionId, actorId: r.actorId, kind: 'DELIVERED' });
return { ok: true };
}
@SubscribeMessage('typing')
typing(@ConnectedSocket() client: Socket, @MessageBody() body: { threadId: string }): void {
const { principal } = client.data as SocketState;
client.to(body.threadId).emit('typing', { threadId: body.threadId, userId: principal.userId });
}
}
@@ -1,8 +1,12 @@
import { Module } from '@nestjs/common';
import { MessageService } from './message.service';
import { MessageGateway } from './message.gateway';
import { SessionVerifier } from '../platform/session.verifier';
import { OutboxModule } from '../outbox/outbox.module';
@Module({
providers: [MessageService],
exports: [MessageService],
imports: [OutboxModule],
providers: [MessageService, MessageGateway, SessionVerifier],
exports: [MessageService, SessionVerifier],
})
export class MessageModule {}
@@ -183,6 +183,31 @@ export class MessageService {
return { interactionId, actorId: actor.id };
}
async markDelivered(
threadId: string,
principal: MessagePrincipal,
interactionId: string,
): Promise<{ interactionId: string; actorId: string }> {
const thread = await this.prisma.iiosThread.findUnique({ where: { id: threadId } });
if (!thread) throw new NotFoundException('thread not found');
const actor = await this.resolveActor(thread.scopeId, principal);
await this.prisma.iiosMessageReceipt.upsert({
where: { interactionId_actorId_receiptKind: { interactionId, actorId: actor.id, receiptKind: 'DELIVERED' } },
create: { interactionId, actorId: actor.id, receiptKind: 'DELIVERED' },
update: { occurredAt: new Date() },
});
return { interactionId, actorId: actor.id };
}
async getMessageById(id: string): Promise<MessageDto | null> {
const i = await this.prisma.iiosInteraction.findUnique({
where: { id },
include: { parts: { orderBy: { partIndex: 'asc' } } },
});
if (!i || !i.threadId) return null;
return this.toDto(i, i.threadId);
}
async history(threadId: string): Promise<MessageDto[]> {
const interactions = await this.prisma.iiosInteraction.findMany({
where: { threadId },
@@ -0,0 +1,46 @@
import { Injectable, UnauthorizedException } from '@nestjs/common';
import jwt from 'jsonwebtoken';
import type { MessagePrincipal } from '../messaging/message.service';
/**
* The real `session` port for P2: verifies a host app's HS256 token (reuses the
* support-service AppTokenVerifier pattern). Per-app secrets come from the
* APP_SECRETS env (JSON map keyed by appId). Expected claims: { sub, name?,
* appId, orgId?, tenantId? }.
*/
@Injectable()
export class SessionVerifier {
private readonly secrets: Record<string, string>;
constructor() {
try {
this.secrets = JSON.parse(process.env.APP_SECRETS ?? '{}');
} catch {
this.secrets = {};
}
}
verify(token: string): MessagePrincipal {
const decoded = jwt.decode(token) as jwt.JwtPayload | null;
const appId = decoded?.appId ? String(decoded.appId) : undefined;
if (!appId) throw new UnauthorizedException('missing appId claim');
const secret = this.secrets[appId];
if (!secret) throw new UnauthorizedException(`unknown app: ${appId}`);
let payload: jwt.JwtPayload;
try {
payload = jwt.verify(token, secret, { algorithms: ['HS256'] }) as jwt.JwtPayload;
} catch {
throw new UnauthorizedException('invalid app token');
}
if (!payload.sub) throw new UnauthorizedException('missing sub claim');
return {
userId: String(payload.sub),
appId,
orgId: payload.orgId ? String(payload.orgId) : `org_${appId}`,
tenantId: payload.tenantId ? String(payload.tenantId) : undefined,
displayName: payload.name ? String(payload.name) : undefined,
};
}
}
+184 -3
View File
@@ -30,10 +30,16 @@ importers:
version: 11.1.27(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)
'@nestjs/core':
specifier: ^11.1.27
version: 11.1.27(@nestjs/common@11.1.27(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.27)(reflect-metadata@0.2.2)(rxjs@7.8.2)
version: 11.1.27(@nestjs/common@11.1.27(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.27)(@nestjs/websockets@11.1.27)(reflect-metadata@0.2.2)(rxjs@7.8.2)
'@nestjs/platform-express':
specifier: ^11.1.27
version: 11.1.27(@nestjs/common@11.1.27(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.27)
'@nestjs/platform-socket.io':
specifier: ^11.1.27
version: 11.1.27(@nestjs/common@11.1.27(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.27)(rxjs@7.8.2)
'@nestjs/websockets':
specifier: ^11.1.27
version: 11.1.27(@nestjs/common@11.1.27(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.27)(@nestjs/platform-socket.io@11.1.27)(reflect-metadata@0.2.2)(rxjs@7.8.2)
'@prisma/client':
specifier: ^6.2.1
version: 6.19.3(prisma@6.19.3(typescript@5.9.3))(typescript@5.9.3)
@@ -52,6 +58,9 @@ importers:
rxjs:
specifier: ^7.8.2
version: 7.8.2
socket.io:
specifier: ^4.8.3
version: 4.8.3
devDependencies:
'@insignia/iios-testkit':
specifier: workspace:*
@@ -501,6 +510,13 @@ packages:
'@nestjs/common': ^11.0.0
'@nestjs/core': ^11.0.0
'@nestjs/platform-socket.io@11.1.27':
resolution: {integrity: sha512-xgpLzaIDGOCC6xOAtHnRAz8sqieFgGxxu3MN5ID026Jt6oeL3efp29N5QHhPr7UlqBfy/Jd02uj0POkZq6Au3Q==}
peerDependencies:
'@nestjs/common': ^11.0.0
'@nestjs/websockets': ^11.0.0
rxjs: ^7.1.0
'@nestjs/schematics@11.1.0':
resolution: {integrity: sha512-lVxGZ46tcdItFMoXr6vyKWlnOsm1SZm/GUqAEDvy2RL4Q4O+3bkziAhrO7Y8JLssFUUvNFEGqAizI52WAxhjDw==}
peerDependencies:
@@ -510,6 +526,18 @@ packages:
prettier:
optional: true
'@nestjs/websockets@11.1.27':
resolution: {integrity: sha512-X3OgJt9KgYTvt9D7sNz9SOj3A1daAHy7DZrYhM1pky8Fh+erlKQH5IQ/tKm+GaJKA5M0srBUr1CMqjak/qNxOw==}
peerDependencies:
'@nestjs/common': ^11.0.0
'@nestjs/core': ^11.0.0
'@nestjs/platform-socket.io': ^11.0.0
reflect-metadata: ^0.1.12 || ^0.2.0
rxjs: ^7.1.0
peerDependenciesMeta:
'@nestjs/platform-socket.io':
optional: true
'@prisma/client@6.19.3':
resolution: {integrity: sha512-mKq3jQFhjvko5LTJFHGilsuQs+W+T3Gm451NzuTDGQxwCzwXHYnIu2zGkRoW+Exq3Rob7yp2MfzSrdIiZVhrBg==}
engines: {node: '>=18.18'}
@@ -665,6 +693,9 @@ packages:
cpu: [x64]
os: [win32]
'@socket.io/component-emitter@3.1.2':
resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==}
'@standard-schema/spec@1.1.0':
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
@@ -684,6 +715,9 @@ packages:
'@types/connect@3.4.38':
resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}
'@types/cors@2.8.19':
resolution: {integrity: sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==}
'@types/deep-eql@4.0.2':
resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
@@ -732,6 +766,9 @@ packages:
'@types/validator@13.15.10':
resolution: {integrity: sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==}
'@types/ws@8.18.1':
resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==}
'@vitest/expect@3.2.6':
resolution: {integrity: sha512-1+7q9BtaKzEmO+fmNT3kYvoNn5Y71XWAx2Q5HRim4tTVRQVRv4uJFAQ5FbK0OPUeNP/WmVCpxYxoJdvuHVjzBQ==}
@@ -812,6 +849,10 @@ packages:
'@xtuc/long@4.2.2':
resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==}
accepts@1.3.8:
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
engines: {node: '>= 0.6'}
accepts@2.0.0:
resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}
engines: {node: '>= 0.6'}
@@ -901,6 +942,10 @@ packages:
base64-js@1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
base64id@2.0.0:
resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==}
engines: {node: ^4.5.0 || >= 5.9}
baseline-browser-mapping@2.10.40:
resolution: {integrity: sha512-BSSLZ9/Cjjv7Gtj5B68ZzXcXUg8iOf3fme+FCuh8rC/Go+Kmh8cox7M3A8dolou16s64QjLPOSdngh7GxXvkSw==}
engines: {node: '>=6.0.0'}
@@ -1154,6 +1199,14 @@ packages:
resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==}
engines: {node: '>= 0.8'}
engine.io-parser@5.2.3:
resolution: {integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==}
engines: {node: '>=10.0.0'}
engine.io@6.6.9:
resolution: {integrity: sha512-clKkw4C7nJ22mGgoVcCg6V/W/TxdNyIOTr89k2ONZu81qqkddPFDF0LXcbAwhzPD8DjkiRCjzuiO6Y+fkpD4vg==}
engines: {node: '>=10.2.0'}
enhanced-resolve@5.24.1:
resolution: {integrity: sha512-7DdUaTjmNwMcH2gLr1qycesKII3BK4RLy/mdAb7x10Lq7bR4aNKHt1BR1ZALSv0rPM/hF5wYF0PhGop/rJm8vw==}
engines: {node: '>=10.13.0'}
@@ -1555,6 +1608,10 @@ packages:
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
negotiator@0.6.3:
resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
engines: {node: '>= 0.6'}
negotiator@1.0.0:
resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
engines: {node: '>= 0.6'}
@@ -1584,6 +1641,10 @@ packages:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'}
object-hash@3.0.0:
resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
engines: {node: '>= 6'}
object-inspect@1.13.4:
resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
engines: {node: '>= 0.4'}
@@ -1787,6 +1848,17 @@ packages:
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
engines: {node: '>=14'}
socket.io-adapter@2.5.8:
resolution: {integrity: sha512-6Oy52pbg+kvdCVvjcN+FnY7BvxZ7cIHNScbvztT/It5d0vbwoJoVZmF2gjJmnV0/4WlXRfG15zc45ySk9Ah8bw==}
socket.io-parser@4.2.6:
resolution: {integrity: sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg==}
engines: {node: '>=10.0.0'}
socket.io@4.8.3:
resolution: {integrity: sha512-2Dd78bqzzjE6KPkD5fHZmDAKRNe3J15q+YHDrIsy9WEkqttc7GY+kT9OBLSMaPbQaEd0x1BjcmtMtXkfpc+T5A==}
engines: {node: '>=10.2.0'}
source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
@@ -2112,6 +2184,18 @@ packages:
wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
ws@8.21.0:
resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==}
engines: {node: '>=10.0.0'}
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: '>=5.0.2'
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
yargs-parser@21.1.1:
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
engines: {node: '>=12'}
@@ -2479,7 +2563,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@nestjs/core@11.1.27(@nestjs/common@11.1.27(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.27)(reflect-metadata@0.2.2)(rxjs@7.8.2)':
'@nestjs/core@11.1.27(@nestjs/common@11.1.27(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.27)(@nestjs/websockets@11.1.27)(reflect-metadata@0.2.2)(rxjs@7.8.2)':
dependencies:
'@nestjs/common': 11.1.27(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)
fast-safe-stringify: 2.1.1
@@ -2491,11 +2575,12 @@ snapshots:
uid: 2.0.2
optionalDependencies:
'@nestjs/platform-express': 11.1.27(@nestjs/common@11.1.27(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.27)
'@nestjs/websockets': 11.1.27(@nestjs/common@11.1.27(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.27)(@nestjs/platform-socket.io@11.1.27)(reflect-metadata@0.2.2)(rxjs@7.8.2)
'@nestjs/platform-express@11.1.27(@nestjs/common@11.1.27(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.27)':
dependencies:
'@nestjs/common': 11.1.27(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)
'@nestjs/core': 11.1.27(@nestjs/common@11.1.27(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.27)(reflect-metadata@0.2.2)(rxjs@7.8.2)
'@nestjs/core': 11.1.27(@nestjs/common@11.1.27(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.27)(@nestjs/websockets@11.1.27)(reflect-metadata@0.2.2)(rxjs@7.8.2)
cors: 2.8.6
express: 5.2.1
multer: 2.1.1
@@ -2504,6 +2589,18 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@nestjs/platform-socket.io@11.1.27(@nestjs/common@11.1.27(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.27)(rxjs@7.8.2)':
dependencies:
'@nestjs/common': 11.1.27(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)
'@nestjs/websockets': 11.1.27(@nestjs/common@11.1.27(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.27)(@nestjs/platform-socket.io@11.1.27)(reflect-metadata@0.2.2)(rxjs@7.8.2)
rxjs: 7.8.2
socket.io: 4.8.3
tslib: 2.8.1
transitivePeerDependencies:
- bufferutil
- supports-color
- utf-8-validate
'@nestjs/schematics@11.1.0(chokidar@4.0.3)(typescript@5.9.3)':
dependencies:
'@angular-devkit/core': 19.2.24(chokidar@4.0.3)
@@ -2515,6 +2612,18 @@ snapshots:
transitivePeerDependencies:
- chokidar
'@nestjs/websockets@11.1.27(@nestjs/common@11.1.27(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.27)(@nestjs/platform-socket.io@11.1.27)(reflect-metadata@0.2.2)(rxjs@7.8.2)':
dependencies:
'@nestjs/common': 11.1.27(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)
'@nestjs/core': 11.1.27(@nestjs/common@11.1.27(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.27)(@nestjs/websockets@11.1.27)(reflect-metadata@0.2.2)(rxjs@7.8.2)
iterare: 1.2.1
object-hash: 3.0.0
reflect-metadata: 0.2.2
rxjs: 7.8.2
tslib: 2.8.1
optionalDependencies:
'@nestjs/platform-socket.io': 11.1.27(@nestjs/common@11.1.27(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.27)(rxjs@7.8.2)
'@prisma/client@6.19.3(prisma@6.19.3(typescript@5.9.3))(typescript@5.9.3)':
optionalDependencies:
prisma: 6.19.3(typescript@5.9.3)
@@ -2625,6 +2734,8 @@ snapshots:
'@rollup/rollup-win32-x64-msvc@4.62.2':
optional: true
'@socket.io/component-emitter@3.1.2': {}
'@standard-schema/spec@1.1.0': {}
'@tokenizer/inflate@0.4.1':
@@ -2650,6 +2761,10 @@ snapshots:
dependencies:
'@types/node': 26.0.1
'@types/cors@2.8.19':
dependencies:
'@types/node': 26.0.1
'@types/deep-eql@4.0.2': {}
'@types/eslint-scope@3.7.7':
@@ -2707,6 +2822,10 @@ snapshots:
'@types/validator@13.15.10': {}
'@types/ws@8.18.1':
dependencies:
'@types/node': 26.0.1
'@vitest/expect@3.2.6':
dependencies:
'@types/chai': 5.2.3
@@ -2829,6 +2948,11 @@ snapshots:
'@xtuc/long@4.2.2': {}
accepts@1.3.8:
dependencies:
mime-types: 2.1.35
negotiator: 0.6.3
accepts@2.0.0:
dependencies:
mime-types: 3.0.2
@@ -2902,6 +3026,8 @@ snapshots:
base64-js@1.5.1: {}
base64id@2.0.0: {}
baseline-browser-mapping@2.10.40: {}
bl@4.1.0:
@@ -3139,6 +3265,25 @@ snapshots:
encodeurl@2.0.0: {}
engine.io-parser@5.2.3: {}
engine.io@6.6.9:
dependencies:
'@types/cors': 2.8.19
'@types/node': 26.0.1
'@types/ws': 8.18.1
accepts: 1.3.8
base64id: 2.0.0
cookie: 0.7.2
cors: 2.8.6
debug: 4.4.3
engine.io-parser: 5.2.3
ws: 8.21.0
transitivePeerDependencies:
- bufferutil
- supports-color
- utf-8-validate
enhanced-resolve@5.24.1:
dependencies:
graceful-fs: 4.2.11
@@ -3555,6 +3700,8 @@ snapshots:
nanoid@3.3.15: {}
negotiator@0.6.3: {}
negotiator@1.0.0: {}
neo-async@2.6.2: {}
@@ -3577,6 +3724,8 @@ snapshots:
object-assign@4.1.1: {}
object-hash@3.0.0: {}
object-inspect@1.13.4: {}
ohash@2.0.11: {}
@@ -3836,6 +3985,36 @@ snapshots:
signal-exit@4.1.0: {}
socket.io-adapter@2.5.8:
dependencies:
debug: 4.4.3
ws: 8.21.0
transitivePeerDependencies:
- bufferutil
- supports-color
- utf-8-validate
socket.io-parser@4.2.6:
dependencies:
'@socket.io/component-emitter': 3.1.2
debug: 4.4.3
transitivePeerDependencies:
- supports-color
socket.io@4.8.3:
dependencies:
accepts: 1.3.8
base64id: 2.0.0
cors: 2.8.6
debug: 4.4.3
engine.io: 6.6.9
socket.io-adapter: 2.5.8
socket.io-parser: 4.2.6
transitivePeerDependencies:
- bufferutil
- supports-color
- utf-8-validate
source-map-js@1.2.1: {}
source-map-support@0.5.21:
@@ -4130,6 +4309,8 @@ snapshots:
wrappy@1.0.2: {}
ws@8.21.0: {}
yargs-parser@21.1.1: {}
yoctocolors-cjs@2.1.3: {}