diff --git a/packages/iios-service/package.json b/packages/iios-service/package.json index ee4bcd8..5343727 100644 --- a/packages/iios-service/package.json +++ b/packages/iios-service/package.json @@ -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", diff --git a/packages/iios-service/src/messaging/message.gateway.ts b/packages/iios-service/src/messaging/message.gateway.ts new file mode 100644 index 0000000..8e7b168 --- /dev/null +++ b/packages/iios-service/src/messaging/message.gateway.ts @@ -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(); + + @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 }); + } +} diff --git a/packages/iios-service/src/messaging/message.module.ts b/packages/iios-service/src/messaging/message.module.ts index cdf9467..614e914 100644 --- a/packages/iios-service/src/messaging/message.module.ts +++ b/packages/iios-service/src/messaging/message.module.ts @@ -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 {} diff --git a/packages/iios-service/src/messaging/message.service.ts b/packages/iios-service/src/messaging/message.service.ts index c51b124..aaf1e24 100644 --- a/packages/iios-service/src/messaging/message.service.ts +++ b/packages/iios-service/src/messaging/message.service.ts @@ -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 { + 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 { const interactions = await this.prisma.iiosInteraction.findMany({ where: { threadId }, diff --git a/packages/iios-service/src/platform/session.verifier.ts b/packages/iios-service/src/platform/session.verifier.ts new file mode 100644 index 0000000..fa7e28d --- /dev/null +++ b/packages/iios-service/src/platform/session.verifier.ts @@ -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; + + 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, + }; + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 954f17b..b1ac7f1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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: {}