chore: websocket
This commit is contained in:
@@ -50,9 +50,11 @@
|
||||
"@nestjs/jwt": "^11.0.0",
|
||||
"@nestjs/passport": "^11.0.5",
|
||||
"@nestjs/platform-fastify": "^11.1.3",
|
||||
"@nestjs/platform-socket.io": "^11.1.3",
|
||||
"@nestjs/schedule": "^6.0.0",
|
||||
"@nestjs/swagger": "^11.2.0",
|
||||
"@nestjs/throttler": "^6.4.0",
|
||||
"@nestjs/websockets": "^11.1.3",
|
||||
"axios": "^1.10.0",
|
||||
"bcrypt": "^5.1.1",
|
||||
"bullmq": "^5.56.1",
|
||||
@@ -67,6 +69,7 @@
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"rxjs": "^7.8.2",
|
||||
"slugify": "^1.6.6",
|
||||
"socket.io": "^4.8.1",
|
||||
"uuid": "^11.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
Generated
+234
-39
@@ -22,7 +22,7 @@ importers:
|
||||
version: 6.4.16
|
||||
'@mikro-orm/nestjs':
|
||||
specifier: ^6.1.1
|
||||
version: 6.1.1(@mikro-orm/core@6.4.16)(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))
|
||||
version: 6.1.1(@mikro-orm/core@6.4.16)(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3)
|
||||
'@mikro-orm/postgresql':
|
||||
specifier: ^6.4.16
|
||||
version: 6.4.16(@mikro-orm/core@6.4.16)
|
||||
@@ -31,22 +31,22 @@ importers:
|
||||
version: 6.4.16(@mikro-orm/core@6.4.16)
|
||||
'@nest-lab/fastify-multer':
|
||||
specifier: ^1.3.0
|
||||
version: 1.3.0(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-fastify@11.1.3(@fastify/static@8.2.0)(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2)))(rxjs@7.8.2)
|
||||
version: 1.3.0(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-fastify@11.1.3(@fastify/static@8.2.0)(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3))(rxjs@7.8.2)
|
||||
'@nest-lab/throttler-storage-redis':
|
||||
specifier: ^1.1.0
|
||||
version: 1.1.0(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/throttler@6.4.0(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2))(ioredis@5.6.1)(reflect-metadata@0.2.2)
|
||||
version: 1.1.0(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3)(@nestjs/throttler@6.4.0(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3)(reflect-metadata@0.2.2))(ioredis@5.6.1)(reflect-metadata@0.2.2)
|
||||
'@nestjs-modules/mailer':
|
||||
specifier: ^2.0.2
|
||||
version: 2.0.2(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(nodemailer@7.0.4)
|
||||
version: 2.0.2(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3)(nodemailer@7.0.4)
|
||||
'@nestjs/axios':
|
||||
specifier: ^4.0.0
|
||||
version: 4.0.0(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.10.0)(rxjs@7.8.2)
|
||||
'@nestjs/bullmq':
|
||||
specifier: ^11.0.2
|
||||
version: 11.0.2(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(bullmq@5.56.1)
|
||||
version: 11.0.2(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3)(bullmq@5.56.1)
|
||||
'@nestjs/cache-manager':
|
||||
specifier: ^3.0.1
|
||||
version: 3.0.1(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(cache-manager@7.0.0)(keyv@5.3.4)(rxjs@7.8.2)
|
||||
version: 3.0.1(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3)(cache-manager@7.0.0)(keyv@5.3.4)(rxjs@7.8.2)
|
||||
'@nestjs/cli':
|
||||
specifier: ^11.0.7
|
||||
version: 11.0.7(@swc/cli@0.6.0(@swc/core@1.12.9)(chokidar@4.0.3))(@swc/core@1.12.9)(@types/node@22.16.0)
|
||||
@@ -58,7 +58,7 @@ importers:
|
||||
version: 4.0.2(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(rxjs@7.8.2)
|
||||
'@nestjs/core':
|
||||
specifier: ^11.1.3
|
||||
version: 11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
version: 11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.3)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
'@nestjs/jwt':
|
||||
specifier: ^11.0.0
|
||||
version: 11.0.0(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))
|
||||
@@ -67,16 +67,22 @@ importers:
|
||||
version: 11.0.5(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(passport@0.7.0)
|
||||
'@nestjs/platform-fastify':
|
||||
specifier: ^11.1.3
|
||||
version: 11.1.3(@fastify/static@8.2.0)(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))
|
||||
version: 11.1.3(@fastify/static@8.2.0)(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3)
|
||||
'@nestjs/platform-socket.io':
|
||||
specifier: ^11.1.3
|
||||
version: 11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.3)(rxjs@7.8.2)
|
||||
'@nestjs/schedule':
|
||||
specifier: ^6.0.0
|
||||
version: 6.0.0(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))
|
||||
version: 6.0.0(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3)
|
||||
'@nestjs/swagger':
|
||||
specifier: ^11.2.0
|
||||
version: 11.2.0(@fastify/static@8.2.0)(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)
|
||||
version: 11.2.0(@fastify/static@8.2.0)(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3)(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)
|
||||
'@nestjs/throttler':
|
||||
specifier: ^6.4.0
|
||||
version: 6.4.0(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)
|
||||
version: 6.4.0(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3)(reflect-metadata@0.2.2)
|
||||
'@nestjs/websockets':
|
||||
specifier: ^11.1.3
|
||||
version: 11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3)(@nestjs/platform-socket.io@11.1.3)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
axios:
|
||||
specifier: ^1.10.0
|
||||
version: 1.10.0
|
||||
@@ -119,6 +125,9 @@ importers:
|
||||
slugify:
|
||||
specifier: ^1.6.6
|
||||
version: 1.6.6
|
||||
socket.io:
|
||||
specifier: ^4.8.1
|
||||
version: 4.8.1
|
||||
uuid:
|
||||
specifier: ^11.1.0
|
||||
version: 11.1.0
|
||||
@@ -140,7 +149,7 @@ importers:
|
||||
version: 11.0.5(chokidar@4.0.3)(typescript@5.8.3)
|
||||
'@nestjs/testing':
|
||||
specifier: ^11.1.3
|
||||
version: 11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))
|
||||
version: 11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3)
|
||||
'@swc/cli':
|
||||
specifier: ^0.6.0
|
||||
version: 0.6.0(@swc/core@1.12.9)(chokidar@4.0.3)
|
||||
@@ -1266,6 +1275,13 @@ packages:
|
||||
'@fastify/view':
|
||||
optional: true
|
||||
|
||||
'@nestjs/platform-socket.io@11.1.3':
|
||||
resolution: {integrity: sha512-jQ+ccprmh3kKolBp+bb97zoaS3vKaiyeNqyctGqV4CSG8P6mXSaaUObWxAsw6Jdgn5YQAVEBWJ6FhvF4s6QZbg==}
|
||||
peerDependencies:
|
||||
'@nestjs/common': ^11.0.0
|
||||
'@nestjs/websockets': ^11.0.0
|
||||
rxjs: ^7.1.0
|
||||
|
||||
'@nestjs/schedule@6.0.0':
|
||||
resolution: {integrity: sha512-aQySMw6tw2nhitELXd3EiRacQRgzUKD9mFcUZVOJ7jPLqIBvXOyvRWLsK9SdurGA+jjziAlMef7iB5ZEFFoQpw==}
|
||||
peerDependencies:
|
||||
@@ -1314,6 +1330,18 @@ packages:
|
||||
'@nestjs/core': ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0
|
||||
reflect-metadata: ^0.1.13 || ^0.2.0
|
||||
|
||||
'@nestjs/websockets@11.1.3':
|
||||
resolution: {integrity: sha512-IjhWKfRf0D247JxYIEs8USblJJbcxUsKJpzbCPaZ7TrVy4LrpG3IRQDlSTOw599TRIYP5ixyH9C0+v5DyaI9uA==}
|
||||
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
|
||||
|
||||
'@noble/hashes@1.8.0':
|
||||
resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==}
|
||||
engines: {node: ^14.21.3 || >=16}
|
||||
@@ -1382,6 +1410,9 @@ packages:
|
||||
'@sinonjs/fake-timers@10.3.0':
|
||||
resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==}
|
||||
|
||||
'@socket.io/component-emitter@3.1.2':
|
||||
resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==}
|
||||
|
||||
'@swc/cli@0.6.0':
|
||||
resolution: {integrity: sha512-Q5FsI3Cw0fGMXhmsg7c08i4EmXCrcl+WnAxb6LYOLHw4JFFC3yzmx9LaXZ7QMbA+JZXbigU2TirI7RAfO0Qlnw==}
|
||||
engines: {node: '>= 16.14.0'}
|
||||
@@ -1521,6 +1552,9 @@ packages:
|
||||
'@types/cookiejar@2.1.5':
|
||||
resolution: {integrity: sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==}
|
||||
|
||||
'@types/cors@2.8.19':
|
||||
resolution: {integrity: sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==}
|
||||
|
||||
'@types/ejs@3.1.5':
|
||||
resolution: {integrity: sha512-nv+GSx77ZtXiJzwKdsASqi+YQ5Z7vwHsTP0JY2SiQgjGckkBRKZnk8nIM+7oUZ1VCtuTz0+By4qVR7fqzp/Dfg==}
|
||||
|
||||
@@ -1897,6 +1931,10 @@ packages:
|
||||
abstract-logging@2.0.1:
|
||||
resolution: {integrity: sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==}
|
||||
|
||||
accepts@1.3.8:
|
||||
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
acorn-jsx@5.3.2:
|
||||
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
|
||||
peerDependencies:
|
||||
@@ -2124,6 +2162,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}
|
||||
|
||||
bcrypt@5.1.1:
|
||||
resolution: {integrity: sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
@@ -2426,6 +2468,10 @@ packages:
|
||||
convert-source-map@2.0.0:
|
||||
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
||||
|
||||
cookie@0.7.2:
|
||||
resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
cookie@1.0.2:
|
||||
resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -2436,6 +2482,10 @@ packages:
|
||||
core-util-is@1.0.3:
|
||||
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
|
||||
|
||||
cors@2.8.5:
|
||||
resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==}
|
||||
engines: {node: '>= 0.10'}
|
||||
|
||||
cosmiconfig-typescript-loader@6.1.0:
|
||||
resolution: {integrity: sha512-tJ1w35ZRUiM5FeTzT7DtYWAFFv37ZLqSRkGi2oeCK1gPhvaWjkAtfXvLmvE1pRfxxp9aQo6ba/Pvg1dKj05D4g==}
|
||||
engines: {node: '>=v18'}
|
||||
@@ -2736,6 +2786,14 @@ packages:
|
||||
resolution: {integrity: sha512-EuJWwlHPZ1LbADuKTClvHtwbaFn4rOD+dRAbWysqEOXRc2Uui0hJInNJrsdH0c+OhJA4nrCBdSkW4DD5YxAo6A==}
|
||||
engines: {node: '>=8.10.0'}
|
||||
|
||||
engine.io-parser@5.2.3:
|
||||
resolution: {integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
|
||||
engine.io@6.6.4:
|
||||
resolution: {integrity: sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==}
|
||||
engines: {node: '>=10.2.0'}
|
||||
|
||||
enhanced-resolve@5.18.2:
|
||||
resolution: {integrity: sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
@@ -4407,6 +4465,10 @@ packages:
|
||||
natural-compare@1.4.0:
|
||||
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
||||
|
||||
negotiator@0.6.3:
|
||||
resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
neo-async@2.6.2:
|
||||
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
|
||||
|
||||
@@ -4493,6 +4555,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'}
|
||||
@@ -5195,6 +5261,17 @@ packages:
|
||||
resolution: {integrity: sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
|
||||
socket.io-adapter@2.5.5:
|
||||
resolution: {integrity: sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==}
|
||||
|
||||
socket.io-parser@4.2.4:
|
||||
resolution: {integrity: sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
|
||||
socket.io@4.8.1:
|
||||
resolution: {integrity: sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==}
|
||||
engines: {node: '>=10.2.0'}
|
||||
|
||||
sonic-boom@4.2.0:
|
||||
resolution: {integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==}
|
||||
|
||||
@@ -5667,6 +5744,10 @@ packages:
|
||||
resolution: {integrity: sha512-BgWVbCI72aIQy937xbawcs+hrVaN/CZ2UwutgaJ36hGqRrLNM+f5LUT/YPRbo8IV/ASeFzXszezV+y2+rq3l8A==}
|
||||
engines: {node: '>= 0.10'}
|
||||
|
||||
vary@1.1.2:
|
||||
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
void-elements@3.1.0:
|
||||
resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -5771,6 +5852,18 @@ packages:
|
||||
resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==}
|
||||
engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
|
||||
|
||||
ws@8.17.1:
|
||||
resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==}
|
||||
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
|
||||
|
||||
xtend@4.0.2:
|
||||
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
|
||||
engines: {node: '>=0.4'}
|
||||
@@ -6797,11 +6890,11 @@ snapshots:
|
||||
- supports-color
|
||||
- tedious
|
||||
|
||||
'@mikro-orm/nestjs@6.1.1(@mikro-orm/core@6.4.16)(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))':
|
||||
'@mikro-orm/nestjs@6.1.1(@mikro-orm/core@6.4.16)(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3)':
|
||||
dependencies:
|
||||
'@mikro-orm/core': 6.4.16
|
||||
'@nestjs/common': 11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
'@nestjs/core': 11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
'@nestjs/core': 11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.3)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
|
||||
'@mikro-orm/postgresql@6.4.16(@mikro-orm/core@6.4.16)':
|
||||
dependencies:
|
||||
@@ -6921,27 +7014,27 @@ snapshots:
|
||||
'@tybys/wasm-util': 0.9.0
|
||||
optional: true
|
||||
|
||||
'@nest-lab/fastify-multer@1.3.0(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-fastify@11.1.3(@fastify/static@8.2.0)(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2)))(rxjs@7.8.2)':
|
||||
'@nest-lab/fastify-multer@1.3.0(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-fastify@11.1.3(@fastify/static@8.2.0)(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3))(rxjs@7.8.2)':
|
||||
dependencies:
|
||||
'@nestjs/common': 11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
'@nestjs/platform-fastify': 11.1.3(@fastify/static@8.2.0)(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))
|
||||
'@nestjs/platform-fastify': 11.1.3(@fastify/static@8.2.0)(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3)
|
||||
fastify-multer: 2.0.3
|
||||
rxjs: 7.8.2
|
||||
|
||||
'@nest-lab/throttler-storage-redis@1.1.0(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/throttler@6.4.0(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2))(ioredis@5.6.1)(reflect-metadata@0.2.2)':
|
||||
'@nest-lab/throttler-storage-redis@1.1.0(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3)(@nestjs/throttler@6.4.0(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3)(reflect-metadata@0.2.2))(ioredis@5.6.1)(reflect-metadata@0.2.2)':
|
||||
dependencies:
|
||||
'@nestjs/common': 11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
'@nestjs/core': 11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
'@nestjs/throttler': 6.4.0(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)
|
||||
'@nestjs/core': 11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.3)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
'@nestjs/throttler': 6.4.0(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3)(reflect-metadata@0.2.2)
|
||||
ioredis: 5.6.1
|
||||
reflect-metadata: 0.2.2
|
||||
tslib: 2.8.1
|
||||
|
||||
'@nestjs-modules/mailer@2.0.2(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(nodemailer@7.0.4)':
|
||||
'@nestjs-modules/mailer@2.0.2(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3)(nodemailer@7.0.4)':
|
||||
dependencies:
|
||||
'@css-inline/css-inline': 0.14.1
|
||||
'@nestjs/common': 11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
'@nestjs/core': 11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
'@nestjs/core': 11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.3)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
glob: 10.3.12
|
||||
nodemailer: 7.0.4
|
||||
optionalDependencies:
|
||||
@@ -6963,24 +7056,24 @@ snapshots:
|
||||
axios: 1.10.0
|
||||
rxjs: 7.8.2
|
||||
|
||||
'@nestjs/bull-shared@11.0.2(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))':
|
||||
'@nestjs/bull-shared@11.0.2(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3)':
|
||||
dependencies:
|
||||
'@nestjs/common': 11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
'@nestjs/core': 11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
'@nestjs/core': 11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.3)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
tslib: 2.8.1
|
||||
|
||||
'@nestjs/bullmq@11.0.2(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(bullmq@5.56.1)':
|
||||
'@nestjs/bullmq@11.0.2(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3)(bullmq@5.56.1)':
|
||||
dependencies:
|
||||
'@nestjs/bull-shared': 11.0.2(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))
|
||||
'@nestjs/bull-shared': 11.0.2(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3)
|
||||
'@nestjs/common': 11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
'@nestjs/core': 11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
'@nestjs/core': 11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.3)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
bullmq: 5.56.1
|
||||
tslib: 2.8.1
|
||||
|
||||
'@nestjs/cache-manager@3.0.1(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(cache-manager@7.0.0)(keyv@5.3.4)(rxjs@7.8.2)':
|
||||
'@nestjs/cache-manager@3.0.1(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3)(cache-manager@7.0.0)(keyv@5.3.4)(rxjs@7.8.2)':
|
||||
dependencies:
|
||||
'@nestjs/common': 11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
'@nestjs/core': 11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
'@nestjs/core': 11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.3)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
cache-manager: 7.0.0
|
||||
keyv: 5.3.4
|
||||
rxjs: 7.8.2
|
||||
@@ -7038,7 +7131,7 @@ snapshots:
|
||||
lodash: 4.17.21
|
||||
rxjs: 7.8.2
|
||||
|
||||
'@nestjs/core@11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2)':
|
||||
'@nestjs/core@11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.3)(reflect-metadata@0.2.2)(rxjs@7.8.2)':
|
||||
dependencies:
|
||||
'@nestjs/common': 11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
'@nuxt/opencollective': 0.4.1
|
||||
@@ -7049,6 +7142,8 @@ snapshots:
|
||||
rxjs: 7.8.2
|
||||
tslib: 2.8.1
|
||||
uid: 2.0.2
|
||||
optionalDependencies:
|
||||
'@nestjs/websockets': 11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3)(@nestjs/platform-socket.io@11.1.3)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
|
||||
'@nestjs/jwt@11.0.0(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))':
|
||||
dependencies:
|
||||
@@ -7069,13 +7164,13 @@ snapshots:
|
||||
'@nestjs/common': 11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
passport: 0.7.0
|
||||
|
||||
'@nestjs/platform-fastify@11.1.3(@fastify/static@8.2.0)(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))':
|
||||
'@nestjs/platform-fastify@11.1.3(@fastify/static@8.2.0)(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3)':
|
||||
dependencies:
|
||||
'@fastify/cors': 11.0.1
|
||||
'@fastify/formbody': 8.0.2
|
||||
'@fastify/middie': 9.0.3
|
||||
'@nestjs/common': 11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
'@nestjs/core': 11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
'@nestjs/core': 11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.3)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
fast-querystring: 1.1.2
|
||||
fastify: 5.3.3
|
||||
light-my-request: 6.6.0
|
||||
@@ -7084,10 +7179,22 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@fastify/static': 8.2.0
|
||||
|
||||
'@nestjs/schedule@6.0.0(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))':
|
||||
'@nestjs/platform-socket.io@11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.3)(rxjs@7.8.2)':
|
||||
dependencies:
|
||||
'@nestjs/common': 11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
'@nestjs/core': 11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
'@nestjs/websockets': 11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3)(@nestjs/platform-socket.io@11.1.3)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
rxjs: 7.8.2
|
||||
socket.io: 4.8.1
|
||||
tslib: 2.8.1
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- supports-color
|
||||
- utf-8-validate
|
||||
|
||||
'@nestjs/schedule@6.0.0(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3)':
|
||||
dependencies:
|
||||
'@nestjs/common': 11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
'@nestjs/core': 11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.3)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
cron: 4.3.0
|
||||
|
||||
'@nestjs/schematics@11.0.5(chokidar@4.0.3)(typescript@5.8.3)':
|
||||
@@ -7101,11 +7208,11 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- chokidar
|
||||
|
||||
'@nestjs/swagger@11.2.0(@fastify/static@8.2.0)(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)':
|
||||
'@nestjs/swagger@11.2.0(@fastify/static@8.2.0)(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3)(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)':
|
||||
dependencies:
|
||||
'@microsoft/tsdoc': 0.15.1
|
||||
'@nestjs/common': 11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
'@nestjs/core': 11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
'@nestjs/core': 11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.3)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
'@nestjs/mapped-types': 2.1.0(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)
|
||||
js-yaml: 4.1.0
|
||||
lodash: 4.17.21
|
||||
@@ -7117,18 +7224,30 @@ snapshots:
|
||||
class-transformer: 0.5.1
|
||||
class-validator: 0.14.2
|
||||
|
||||
'@nestjs/testing@11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))':
|
||||
'@nestjs/testing@11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3)':
|
||||
dependencies:
|
||||
'@nestjs/common': 11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
'@nestjs/core': 11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
'@nestjs/core': 11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.3)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
tslib: 2.8.1
|
||||
|
||||
'@nestjs/throttler@6.4.0(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)':
|
||||
'@nestjs/throttler@6.4.0(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3)(reflect-metadata@0.2.2)':
|
||||
dependencies:
|
||||
'@nestjs/common': 11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
'@nestjs/core': 11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
'@nestjs/core': 11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.3)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
reflect-metadata: 0.2.2
|
||||
|
||||
'@nestjs/websockets@11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3)(@nestjs/platform-socket.io@11.1.3)(reflect-metadata@0.2.2)(rxjs@7.8.2)':
|
||||
dependencies:
|
||||
'@nestjs/common': 11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
'@nestjs/core': 11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.3)(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.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.3)(rxjs@7.8.2)
|
||||
|
||||
'@noble/hashes@1.8.0': {}
|
||||
|
||||
'@nodelib/fs.scandir@2.1.5':
|
||||
@@ -7191,6 +7310,8 @@ snapshots:
|
||||
dependencies:
|
||||
'@sinonjs/commons': 3.0.1
|
||||
|
||||
'@socket.io/component-emitter@3.1.2': {}
|
||||
|
||||
'@swc/cli@0.6.0(@swc/core@1.12.9)(chokidar@4.0.3)':
|
||||
dependencies:
|
||||
'@swc/core': 1.12.9
|
||||
@@ -7325,6 +7446,10 @@ snapshots:
|
||||
|
||||
'@types/cookiejar@2.1.5': {}
|
||||
|
||||
'@types/cors@2.8.19':
|
||||
dependencies:
|
||||
'@types/node': 22.16.0
|
||||
|
||||
'@types/ejs@3.1.5':
|
||||
optional: true
|
||||
|
||||
@@ -7778,6 +7903,11 @@ snapshots:
|
||||
|
||||
abstract-logging@2.0.1: {}
|
||||
|
||||
accepts@1.3.8:
|
||||
dependencies:
|
||||
mime-types: 2.1.35
|
||||
negotiator: 0.6.3
|
||||
|
||||
acorn-jsx@5.3.2(acorn@8.15.0):
|
||||
dependencies:
|
||||
acorn: 8.15.0
|
||||
@@ -8037,6 +8167,8 @@ snapshots:
|
||||
|
||||
base64-js@1.5.1: {}
|
||||
|
||||
base64id@2.0.0: {}
|
||||
|
||||
bcrypt@5.1.1:
|
||||
dependencies:
|
||||
'@mapbox/node-pre-gyp': 1.0.11
|
||||
@@ -8378,12 +8510,19 @@ snapshots:
|
||||
|
||||
convert-source-map@2.0.0: {}
|
||||
|
||||
cookie@0.7.2: {}
|
||||
|
||||
cookie@1.0.2: {}
|
||||
|
||||
cookiejar@2.1.4: {}
|
||||
|
||||
core-util-is@1.0.3: {}
|
||||
|
||||
cors@2.8.5:
|
||||
dependencies:
|
||||
object-assign: 4.1.1
|
||||
vary: 1.1.2
|
||||
|
||||
cosmiconfig-typescript-loader@6.1.0(@types/node@22.16.0)(cosmiconfig@9.0.0(typescript@5.8.3))(typescript@5.8.3):
|
||||
dependencies:
|
||||
'@types/node': 22.16.0
|
||||
@@ -8678,6 +8817,24 @@ snapshots:
|
||||
encoding-japanese@2.2.0:
|
||||
optional: true
|
||||
|
||||
engine.io-parser@5.2.3: {}
|
||||
|
||||
engine.io@6.6.4:
|
||||
dependencies:
|
||||
'@types/cors': 2.8.19
|
||||
'@types/node': 22.16.0
|
||||
accepts: 1.3.8
|
||||
base64id: 2.0.0
|
||||
cookie: 0.7.2
|
||||
cors: 2.8.5
|
||||
debug: 4.3.4
|
||||
engine.io-parser: 5.2.3
|
||||
ws: 8.17.1
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- supports-color
|
||||
- utf-8-validate
|
||||
|
||||
enhanced-resolve@5.18.2:
|
||||
dependencies:
|
||||
graceful-fs: 4.2.11
|
||||
@@ -10963,6 +11120,8 @@ snapshots:
|
||||
|
||||
natural-compare@1.4.0: {}
|
||||
|
||||
negotiator@0.6.3: {}
|
||||
|
||||
neo-async@2.6.2: {}
|
||||
|
||||
nice-try@1.0.5:
|
||||
@@ -11039,6 +11198,8 @@ snapshots:
|
||||
|
||||
object-assign@4.1.1: {}
|
||||
|
||||
object-hash@3.0.0: {}
|
||||
|
||||
object-inspect@1.13.4: {}
|
||||
|
||||
object-keys@1.1.1: {}
|
||||
@@ -11796,6 +11957,36 @@ snapshots:
|
||||
|
||||
slugify@1.6.6: {}
|
||||
|
||||
socket.io-adapter@2.5.5:
|
||||
dependencies:
|
||||
debug: 4.3.4
|
||||
ws: 8.17.1
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- supports-color
|
||||
- utf-8-validate
|
||||
|
||||
socket.io-parser@4.2.4:
|
||||
dependencies:
|
||||
'@socket.io/component-emitter': 3.1.2
|
||||
debug: 4.3.4
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
socket.io@4.8.1:
|
||||
dependencies:
|
||||
accepts: 1.3.8
|
||||
base64id: 2.0.0
|
||||
cors: 2.8.5
|
||||
debug: 4.3.4
|
||||
engine.io: 6.6.4
|
||||
socket.io-adapter: 2.5.5
|
||||
socket.io-parser: 4.2.4
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- supports-color
|
||||
- utf-8-validate
|
||||
|
||||
sonic-boom@4.2.0:
|
||||
dependencies:
|
||||
atomic-sleep: 1.0.0
|
||||
@@ -12302,6 +12493,8 @@ snapshots:
|
||||
|
||||
validator@13.15.15: {}
|
||||
|
||||
vary@1.1.2: {}
|
||||
|
||||
void-elements@3.1.0:
|
||||
optional: true
|
||||
|
||||
@@ -12469,6 +12662,8 @@ snapshots:
|
||||
imurmurhash: 0.1.4
|
||||
signal-exit: 3.0.7
|
||||
|
||||
ws@8.17.1: {}
|
||||
|
||||
xtend@4.0.2: {}
|
||||
|
||||
y18n@5.0.8: {}
|
||||
|
||||
@@ -4,12 +4,14 @@ import { BullModule } from "@nestjs/bullmq";
|
||||
import { CacheModule } from "@nestjs/cache-manager";
|
||||
import { MiddlewareConsumer, Module, NestModule } from "@nestjs/common";
|
||||
import { ConfigModule } from "@nestjs/config";
|
||||
import { JwtModule } from "@nestjs/jwt";
|
||||
import { ScheduleModule } from "@nestjs/schedule";
|
||||
import { ThrottlerModule } from "@nestjs/throttler";
|
||||
|
||||
import { bullMqConfig } from "./configs/bullmq.config";
|
||||
import { cacheConfig } from "./configs/cache.config";
|
||||
import { httpConfig } from "./configs/http.config";
|
||||
import { jwtConfig } from "./configs/jwt.config";
|
||||
import { databaseConfig } from "./configs/mikro-orm.config";
|
||||
import { rateLimitConfig } from "./configs/rateLimit.config";
|
||||
import { HTTPLogger } from "./core/middlewares/logger.middleware";
|
||||
@@ -25,6 +27,7 @@ import { UtilsModule } from "./modules/utils/utils.module";
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule.forRoot({ isGlobal: true, cache: true }),
|
||||
JwtModule.registerAsync(jwtConfig()),
|
||||
ScheduleModule.forRoot(),
|
||||
BullModule.forRootAsync(bullMqConfig()),
|
||||
CacheModule.registerAsync(cacheConfig()),
|
||||
|
||||
@@ -62,6 +62,7 @@ export const enum AuthMessage {
|
||||
TOKEN_EXPIRED_OR_INVALID = "توکن منقضی شده یا نامعتبر است",
|
||||
ACCESS_DENIED = "دسترسی شما محدود شده است",
|
||||
USER_ACCOUNT_INACTIVE = "حساب کاربری شما غیر فعال است",
|
||||
TOKEN_MISSED_OR_EXPIRED = "TOKEN_MISSED_OR_EXPIRED",
|
||||
}
|
||||
|
||||
export const enum UserMessage {
|
||||
@@ -201,6 +202,97 @@ export const enum EmailMessage {
|
||||
MESSAGE_MOVED_TO_FAVORITE_SUCCESSFULLY = "پیام با موفقیت به علاقه مندی ها منتقل شد",
|
||||
}
|
||||
|
||||
export const enum WebSocketMessage {
|
||||
// Connection Messages
|
||||
WS_CONNECTION_ESTABLISHED = "اتصال وب سوکت با موفقیت برقرار شد",
|
||||
WS_CONNECTION_FAILED = "اتصال وب سوکت ناموفق بود",
|
||||
WS_AUTHENTICATION_REQUIRED = "احراز هویت وب سوکت مورد نیاز است",
|
||||
WS_AUTHENTICATION_FAILED = "احراز هویت وب سوکت ناموفق بود",
|
||||
WS_AUTHENTICATION_SUCCESS = "احراز هویت وب سوکت با موفقیت انجام شد",
|
||||
WS_INVALID_TOKEN = "توکن وب سوکت نامعتبر است",
|
||||
WS_TOKEN_EXPIRED = "توکن وب سوکت منقضی شده است",
|
||||
|
||||
// Room Management Messages
|
||||
JOINED_ROOM_SUCCESS = "با موفقیت به اتاق متصل شدید",
|
||||
LEFT_ROOM_SUCCESS = "با موفقیت از اتاق خارج شدید",
|
||||
ROOM_NOT_FOUND = "اتاق یافت نشد",
|
||||
UNAUTHORIZED_ROOM_ACCESS = "دسترسی غیرمجاز به اتاق",
|
||||
|
||||
// Session Management Messages
|
||||
WS_SESSION_CREATED = "جلسه وب سوکت ایجاد شد",
|
||||
WS_SESSION_JOINED = "به جلسه وب سوکت پیوستید",
|
||||
WS_SESSION_LEFT = "از جلسه وب سوکت خارج شدید",
|
||||
WS_SESSION_EXPIRED = "جلسه وب سوکت منقضی شده است",
|
||||
WS_SESSION_NOT_FOUND = "جلسه وب سوکت یافت نشد",
|
||||
|
||||
// Error Messages
|
||||
WS_CONNECTION_ERROR = "خطا در اتصال وب سوکت",
|
||||
INVALID_EVENT = "رویداد نامعتبر",
|
||||
INVALID_DATA = "داده نامعتبر",
|
||||
WS_RATE_LIMIT_EXCEEDED = "تعداد درخواستهای وب سوکت از حد مجاز بیشتر است",
|
||||
INTERNAL_ERROR = "خطای داخلی سرور",
|
||||
|
||||
// Notification Messages
|
||||
NEW_MESSAGE_NOTIFICATION = "پیام جدید دریافت شد",
|
||||
MESSAGE_STATUS_UPDATED = "وضعیت پیام بهروزرسانی شد",
|
||||
MESSAGE_MOVED_NOTIFICATION = "پیام جابجا شد",
|
||||
MESSAGE_DELETED_NOTIFICATION = "پیام حذف شد",
|
||||
|
||||
// General Messages
|
||||
OPERATION_SUCCESS = "عملیات با موفقیت انجام شد",
|
||||
OPERATION_FAILED = "عملیات ناموفق بود",
|
||||
PERMISSION_DENIED = "مجوز دسترسی ندارید",
|
||||
RESOURCE_NOT_FOUND = "منبع یافت نشد",
|
||||
|
||||
// Authentication errors
|
||||
AUTHENTICATION_REQUIRED = "احراز هویت ضروری است",
|
||||
INVALID_TOKEN = "توکن احراز هویت نامعتبر است",
|
||||
TOKEN_EXPIRED = "توکن منقضی شده است",
|
||||
UNAUTHORIZED_ACCESS = "دسترسی غیرمجاز",
|
||||
USER_NOT_AUTHENTICATED = "کاربر احراز هویت نشده است",
|
||||
|
||||
// Connection errors
|
||||
CONNECTION_FAILED = "اتصال برقرار نشد",
|
||||
WEBSOCKET_ERROR = "خطا در ارتباط WebSocket",
|
||||
CONNECTION_TIMEOUT = "زمان اتصال به پایان رسید",
|
||||
CONNECTION_REFUSED = "اتصال رد شد",
|
||||
|
||||
// Session errors
|
||||
SESSION_NOT_FOUND = "جلسه چت یافت نشد",
|
||||
SESSION_CREATION_FAILED = "ایجاد جلسه چت با شکست مواجه شد",
|
||||
SESSION_ACCESS_DENIED = "دسترسی به جلسه چت مجاز نیست",
|
||||
SESSION_EXPIRED = "جلسه چت منقضی شده است",
|
||||
SESSION_ALREADY_EXISTS = "جلسه چت قبلاً وجود دارد",
|
||||
|
||||
// Message errors
|
||||
MESSAGE_TOO_LONG = "پیام از حداکثر طول مجاز تجاوز کرده است",
|
||||
MESSAGE_EMPTY = "پیام نمیتواند خالی باشد",
|
||||
MESSAGE_SEND_FAILED = "ارسال پیام با شکست مواجه شد",
|
||||
INVALID_MESSAGE_FORMAT = "فرمت پیام نامعتبر است",
|
||||
|
||||
// Rate limiting
|
||||
RATE_LIMIT_EXCEEDED = "تعداد پیامهای ارسالی بیش از حد مجاز است",
|
||||
TOO_MANY_CONNECTIONS = "تعداد اتصالات بیش از حد مجاز است",
|
||||
|
||||
// Service errors
|
||||
LLM_SERVICE_ERROR = "سرویس هوش مصنوعی موقتاً در دسترس نیست",
|
||||
SERVICE_UNAVAILABLE = "سرویس موقتاً در دسترس نیست",
|
||||
INTERNAL_SERVER_ERROR = ".خطای داخلی سرور",
|
||||
|
||||
// Validation errors
|
||||
INVALID_SESSION_ID = "شناسه جلسه نامعتبر است",
|
||||
INVALID_USER_ID = "شناسه کاربر نامعتبر است",
|
||||
INVALID_MESSAGE_ID = "شناسه پیام نامعتبر است",
|
||||
REQUIRED_FIELD_MISSING = "فیلد ضروری موجود نیست",
|
||||
|
||||
// Success messages
|
||||
AUTHENTICATED = "احراز هویت موفقیتآمیز",
|
||||
SESSION_CREATED = "جلسه چت با موفقیت ایجاد شد",
|
||||
CHAT_JOINED = "با موفقیت به چت متصل شدید",
|
||||
MESSAGE_SENT = "پیام ارسال شد",
|
||||
CONNECTION_ESTABLISHED = "اتصال برقرار شد",
|
||||
}
|
||||
|
||||
export const enum SmsMessage {
|
||||
SEND_SMS_ERROR = "خطا در ارسال پیامک",
|
||||
}
|
||||
|
||||
@@ -4,8 +4,10 @@ import { JwtModuleAsyncOptions } from "@nestjs/jwt";
|
||||
export function jwtConfig(): JwtModuleAsyncOptions {
|
||||
return {
|
||||
inject: [ConfigService],
|
||||
|
||||
useFactory: (configService: ConfigService) => {
|
||||
return {
|
||||
global: true,
|
||||
secret: configService.getOrThrow<string>("JWT_SECRET"),
|
||||
signOptions: {
|
||||
issuer: configService.getOrThrow<string>("JWT_ISSUER"),
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
import { ArgumentsHost, Catch, HttpException, Logger, WsExceptionFilter as NestWsExceptionFilter } from "@nestjs/common";
|
||||
import { WsException } from "@nestjs/websockets";
|
||||
import { Socket } from "socket.io";
|
||||
|
||||
import { WebSocketMessage } from "../../common/enums/message.enum";
|
||||
import { WEBSOCKET_EVENTS } from "../../modules/email/constants/email-events.constant";
|
||||
|
||||
export interface WebSocketErrorResponse {
|
||||
error: string;
|
||||
message: string;
|
||||
timestamp: string;
|
||||
event?: string;
|
||||
statusCode?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Global WebSocket exception filter
|
||||
* Catches and handles all WebSocket-related errors
|
||||
*/
|
||||
@Catch()
|
||||
export class WsExceptionFilter implements NestWsExceptionFilter {
|
||||
private readonly logger = new Logger(WsExceptionFilter.name);
|
||||
|
||||
catch(exception: Error, host: ArgumentsHost): void {
|
||||
const client: Socket = host.switchToWs().getClient<Socket>();
|
||||
const data = host.switchToWs().getData();
|
||||
|
||||
const errorResponse = this.createErrorResponse(exception, data);
|
||||
|
||||
// Log the error for monitoring
|
||||
this.logError(exception, client, data);
|
||||
|
||||
// Emit error to client
|
||||
client.emit(WEBSOCKET_EVENTS.CONNECTION_ERROR, errorResponse);
|
||||
|
||||
// Handle specific error types
|
||||
this.handleSpecificErrors(exception, client);
|
||||
}
|
||||
|
||||
private createErrorResponse(exception: Error, data?: any): WebSocketErrorResponse {
|
||||
let error: string;
|
||||
let message: string;
|
||||
let statusCode: number | undefined;
|
||||
|
||||
if (exception instanceof WsException) {
|
||||
const wsError = exception.getError();
|
||||
|
||||
if (typeof wsError === "string") {
|
||||
error = wsError;
|
||||
message = wsError;
|
||||
} else if (typeof wsError === "object" && wsError !== null) {
|
||||
error = (wsError as any).error || "WebSocket Error";
|
||||
message = (wsError as any).message || "An error occurred";
|
||||
statusCode = (wsError as any).statusCode;
|
||||
} else {
|
||||
error = "WebSocket Error";
|
||||
message = "An unknown WebSocket error occurred";
|
||||
}
|
||||
} else if (exception instanceof HttpException) {
|
||||
statusCode = exception.getStatus();
|
||||
const response = exception.getResponse();
|
||||
|
||||
if (typeof response === "string") {
|
||||
error = response;
|
||||
message = response;
|
||||
} else if (typeof response === "object" && response !== null) {
|
||||
error = (response as any).error || "HTTP Error";
|
||||
message = (response as any).message || exception.message;
|
||||
} else {
|
||||
error = "HTTP Error";
|
||||
message = exception.message;
|
||||
}
|
||||
} else {
|
||||
// Generic error handling
|
||||
error = "Internal Server Error";
|
||||
message = this.getSafeErrorMessage(exception);
|
||||
}
|
||||
|
||||
return {
|
||||
error,
|
||||
message,
|
||||
timestamp: new Date().toISOString(),
|
||||
event: data?.event || "unknown",
|
||||
statusCode,
|
||||
};
|
||||
}
|
||||
|
||||
private logError(exception: Error, client: Socket, data?: any): void {
|
||||
const errorContext = {
|
||||
clientId: client.id,
|
||||
userId: client.data?.user?.id,
|
||||
sessionId: client.data?.sessionId,
|
||||
event: data?.event,
|
||||
errorType: exception.constructor.name,
|
||||
errorMessage: exception.message,
|
||||
stack: exception.stack,
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
|
||||
if (exception instanceof WsException) {
|
||||
this.logger.warn("WebSocket exception occurred", errorContext);
|
||||
} else if (exception instanceof HttpException) {
|
||||
this.logger.warn("HTTP exception in WebSocket context", {
|
||||
...errorContext,
|
||||
statusCode: exception.getStatus(),
|
||||
});
|
||||
} else {
|
||||
this.logger.error("Unexpected error in WebSocket context", errorContext);
|
||||
}
|
||||
}
|
||||
|
||||
private handleSpecificErrors(exception: Error, client: Socket): void {
|
||||
if (exception instanceof WsException) {
|
||||
const wsError = exception.getError();
|
||||
|
||||
// Handle authentication errors
|
||||
if (this.isAuthenticationError(wsError)) {
|
||||
this.handleAuthenticationError(client);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle permission errors
|
||||
if (this.isPermissionError(wsError)) {
|
||||
this.handlePermissionError(client);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle rate limiting errors
|
||||
if (this.isRateLimitError(wsError)) {
|
||||
this.handleRateLimitError(client);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle connection errors
|
||||
if (this.isConnectionError(exception)) {
|
||||
this.handleConnectionError(client);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle validation errors
|
||||
if (this.isValidationError(exception)) {
|
||||
this.handleValidationError(client);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private isAuthenticationError(error: any): boolean {
|
||||
if (typeof error === "string") {
|
||||
return error.includes("authentication") || error.includes("token") || error.includes("unauthorized");
|
||||
}
|
||||
if (typeof error === "object" && error !== null) {
|
||||
const errorMessage = (error as any).message || (error as any).error || "";
|
||||
return errorMessage.includes("authentication") || errorMessage.includes("token") || errorMessage.includes("unauthorized");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private isPermissionError(error: any): boolean {
|
||||
if (typeof error === "string") {
|
||||
return error.includes("permission") || error.includes("access denied") || error.includes("forbidden");
|
||||
}
|
||||
if (typeof error === "object" && error !== null) {
|
||||
const errorMessage = (error as any).message || (error as any).error || "";
|
||||
return errorMessage.includes("permission") || errorMessage.includes("access denied") || errorMessage.includes("forbidden");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private isRateLimitError(error: any): boolean {
|
||||
if (typeof error === "string") {
|
||||
return error.includes("rate limit") || error.includes("too many requests");
|
||||
}
|
||||
if (typeof error === "object" && error !== null) {
|
||||
const errorMessage = (error as any).message || (error as any).error || "";
|
||||
return errorMessage.includes("rate limit") || errorMessage.includes("too many requests");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private isConnectionError(exception: Error): boolean {
|
||||
return exception.message.includes("connection") || exception.message.includes("disconnect") || exception.message.includes("timeout");
|
||||
}
|
||||
|
||||
private isValidationError(exception: Error): boolean {
|
||||
return exception.message.includes("validation") || exception.message.includes("invalid") || exception.name.includes("ValidationError");
|
||||
}
|
||||
|
||||
private handleAuthenticationError(client: Socket): void {
|
||||
client.emit(WEBSOCKET_EVENTS.AUTHENTICATION_FAILED, {
|
||||
message: WebSocketMessage.WS_AUTHENTICATION_FAILED,
|
||||
timestamp: new Date().toISOString(),
|
||||
reconnectAllowed: true,
|
||||
});
|
||||
|
||||
// Disconnect after a short delay to allow client to handle the error
|
||||
setTimeout(() => {
|
||||
if (client.connected) {
|
||||
client.disconnect(true);
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
|
||||
private handlePermissionError(client: Socket): void {
|
||||
client.emit(WEBSOCKET_EVENTS.CONNECTION_ERROR, {
|
||||
error: WebSocketMessage.PERMISSION_DENIED,
|
||||
message: WebSocketMessage.PERMISSION_DENIED,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
|
||||
private handleRateLimitError(client: Socket): void {
|
||||
client.emit(WEBSOCKET_EVENTS.CONNECTION_ERROR, {
|
||||
error: WebSocketMessage.WS_RATE_LIMIT_EXCEEDED,
|
||||
message: WebSocketMessage.WS_RATE_LIMIT_EXCEEDED,
|
||||
timestamp: new Date().toISOString(),
|
||||
retryAfter: 60000, // 1 minute
|
||||
});
|
||||
}
|
||||
|
||||
private handleConnectionError(client: Socket): void {
|
||||
client.emit(WEBSOCKET_EVENTS.CONNECTION_ERROR, {
|
||||
error: WebSocketMessage.WS_CONNECTION_ERROR,
|
||||
message: WebSocketMessage.WS_CONNECTION_ERROR,
|
||||
timestamp: new Date().toISOString(),
|
||||
reconnectAllowed: true,
|
||||
});
|
||||
}
|
||||
|
||||
private handleValidationError(client: Socket): void {
|
||||
client.emit(WEBSOCKET_EVENTS.CONNECTION_ERROR, {
|
||||
error: WebSocketMessage.INVALID_DATA,
|
||||
message: WebSocketMessage.INVALID_DATA,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
|
||||
private getSafeErrorMessage(exception: Error): string {
|
||||
// In production, don't expose internal error details
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
return WebSocketMessage.INTERNAL_ERROR;
|
||||
}
|
||||
|
||||
return exception.message || WebSocketMessage.INTERNAL_ERROR;
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import { Logger, ValidationPipe } from "@nestjs/common";
|
||||
import { ConfigService } from "@nestjs/config";
|
||||
import { NestFactory } from "@nestjs/core";
|
||||
import { FastifyAdapter, NestFastifyApplication } from "@nestjs/platform-fastify";
|
||||
import { IoAdapter } from "@nestjs/platform-socket.io";
|
||||
|
||||
import { AppModule } from "./app.module";
|
||||
import { getSwaggerDocument } from "./configs/swagger.config";
|
||||
@@ -25,6 +26,9 @@ async function bootstrap() {
|
||||
|
||||
const app = await NestFactory.create<NestFastifyApplication>(AppModule, fastifyAdapter);
|
||||
|
||||
// Setup WebSocket adapter
|
||||
app.useWebSocketAdapter(new IoAdapter(app));
|
||||
|
||||
app.useGlobalPipes(new ValidationPipe({ transform: true, whitelist: true, forbidNonWhitelisted: true }));
|
||||
|
||||
app.useGlobalInterceptors(new ResponseInterceptor(), new PaginationInterceptor());
|
||||
@@ -37,6 +41,7 @@ async function bootstrap() {
|
||||
credentials: true,
|
||||
optionsSuccessStatus: 204,
|
||||
});
|
||||
|
||||
const configService = app.get<ConfigService>(ConfigService);
|
||||
|
||||
getSwaggerDocument(app);
|
||||
@@ -51,8 +56,10 @@ async function bootstrap() {
|
||||
await app.listen(PORT, "0.0.0.0", () => {
|
||||
logger.log(`Application is running on: http://localhost:${PORT}`);
|
||||
logger.log(`Swagger documentation: http://localhost:${PORT}/api-docs`);
|
||||
logger.log(`WebSocket server is running on: ws://localhost:${PORT}/email`);
|
||||
});
|
||||
}
|
||||
|
||||
bootstrap().catch((error) => {
|
||||
const logger = new Logger("Bootstrap");
|
||||
logger.error("Failed to start application", error);
|
||||
|
||||
@@ -0,0 +1,240 @@
|
||||
import { CanActivate, ExecutionContext, Injectable, Logger } from "@nestjs/common";
|
||||
import { WsException } from "@nestjs/websockets";
|
||||
import { Socket } from "socket.io";
|
||||
|
||||
import { WebSocketMessage } from "../../../common/enums/message.enum";
|
||||
import { WebSocketAuthService } from "../../email/services/websocket-auth.service";
|
||||
|
||||
/**
|
||||
* WebSocket authentication guard
|
||||
* Protects WebSocket endpoints by verifying JWT tokens
|
||||
*/
|
||||
@Injectable()
|
||||
export class WsAuthGuard implements CanActivate {
|
||||
private readonly logger = new Logger(WsAuthGuard.name);
|
||||
|
||||
constructor(private readonly authService: WebSocketAuthService) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
try {
|
||||
const client: Socket = context.switchToWs().getClient<Socket>();
|
||||
const data = context.switchToWs().getData();
|
||||
|
||||
// Skip authentication for certain events
|
||||
if (this.shouldSkipAuthentication(data?.event)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if client is already authenticated
|
||||
if (this.isClientAuthenticated(client)) {
|
||||
// Update last activity
|
||||
this.authService.updateLastActivity(client);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Attempt to authenticate client
|
||||
const authResult = await this.authService.authenticateClient(client);
|
||||
|
||||
if (!authResult.success) {
|
||||
this.logger.warn("WebSocket authentication failed in guard", {
|
||||
clientId: client.id,
|
||||
event: data?.event,
|
||||
error: authResult.error,
|
||||
});
|
||||
|
||||
throw new WsException({
|
||||
error: authResult.error || WebSocketMessage.WS_AUTHENTICATION_FAILED,
|
||||
message: authResult.error || WebSocketMessage.WS_AUTHENTICATION_FAILED,
|
||||
statusCode: 401,
|
||||
});
|
||||
}
|
||||
|
||||
this.logger.log("WebSocket authentication successful in guard", {
|
||||
clientId: client.id,
|
||||
userId: authResult.user?.id,
|
||||
event: data?.event,
|
||||
});
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
this.logger.error("WebSocket guard error", {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
context: context.getType(),
|
||||
});
|
||||
|
||||
if (error instanceof WsException) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
throw new WsException({
|
||||
error: WebSocketMessage.WS_AUTHENTICATION_FAILED,
|
||||
message: WebSocketMessage.WS_AUTHENTICATION_FAILED,
|
||||
statusCode: 401,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if client is already authenticated
|
||||
*/
|
||||
private isClientAuthenticated(client: Socket): boolean {
|
||||
return !!(client.data?.user?.id && client.data?.connectedAt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if authentication should be skipped for certain events
|
||||
*/
|
||||
private shouldSkipAuthentication(event?: string): boolean {
|
||||
const skipEvents = ["connect", "disconnect", "ping", "pong", "error", "connection_error", "authenticate"];
|
||||
|
||||
return skipEvents.includes(event || "");
|
||||
}
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Optional: Role-based WebSocket guard
|
||||
// * Extends authentication to include role-based access control
|
||||
// */
|
||||
// @Injectable()
|
||||
// export class WsRoleGuard implements CanActivate {
|
||||
// private readonly logger = new Logger(WsRoleGuard.name);
|
||||
// private readonly requiredRoles: string[] = [];
|
||||
|
||||
// constructor(
|
||||
// private readonly authService: WebSocketAuthService,
|
||||
// requiredRoles: string[] = [],
|
||||
// ) {
|
||||
// this.requiredRoles = requiredRoles;
|
||||
// }
|
||||
|
||||
// async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
// try {
|
||||
// const client: Socket = context.switchToWs().getClient<Socket>();
|
||||
// const data = context.switchToWs().getData();
|
||||
|
||||
// // First check authentication
|
||||
// if (!this.isClientAuthenticated(client)) {
|
||||
// throw new WsException({
|
||||
// error: WebSocketMessage.WS_AUTHENTICATION_REQUIRED,
|
||||
// message: WebSocketMessage.WS_AUTHENTICATION_REQUIRED,
|
||||
// statusCode: 401,
|
||||
// });
|
||||
// }
|
||||
|
||||
// // Then check roles if required
|
||||
// if (this.requiredRoles.length > 0) {
|
||||
// const user = client.data.user;
|
||||
// const hasRequiredRole = this.checkUserRoles(user.permissions || [], this.requiredRoles);
|
||||
|
||||
// if (!hasRequiredRole && !user.isAdmin) {
|
||||
// this.logger.warn("WebSocket role check failed", {
|
||||
// clientId: client.id,
|
||||
// userId: user.id,
|
||||
// userRoles: user.permissions,
|
||||
// requiredRoles: this.requiredRoles,
|
||||
// event: data?.event,
|
||||
// });
|
||||
|
||||
// throw new WsException({
|
||||
// error: WebSocketMessage.PERMISSION_DENIED,
|
||||
// message: WebSocketMessage.PERMISSION_DENIED,
|
||||
// statusCode: 403,
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
||||
// return true;
|
||||
// } catch (error) {
|
||||
// this.logger.error("WebSocket role guard error", {
|
||||
// error: error instanceof Error ? error.message : String(error),
|
||||
// requiredRoles: this.requiredRoles,
|
||||
// });
|
||||
|
||||
// if (error instanceof WsException) {
|
||||
// throw error;
|
||||
// }
|
||||
|
||||
// throw new WsException({
|
||||
// error: WebSocketMessage.PERMISSION_DENIED,
|
||||
// message: WebSocketMessage.PERMISSION_DENIED,
|
||||
// statusCode: 403,
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
||||
// private isClientAuthenticated(client: Socket): boolean {
|
||||
// return !!(client.data?.user?.id && client.data?.connectedAt);
|
||||
// }
|
||||
|
||||
// private checkUserRoles(userRoles: string[], requiredRoles: string[]): boolean {
|
||||
// return requiredRoles.some((role) => userRoles.includes(role));
|
||||
// }
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Room-based WebSocket guard
|
||||
// * Checks if user has permission to access specific rooms
|
||||
// */
|
||||
// @Injectable()
|
||||
// export class WsRoomGuard implements CanActivate {
|
||||
// private readonly logger = new Logger(WsRoomGuard.name);
|
||||
|
||||
// constructor(private readonly authService: WebSocketAuthService) {}
|
||||
|
||||
// async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
// try {
|
||||
// const client: Socket = context.switchToWs().getClient<Socket>();
|
||||
// const data = context.switchToWs().getData();
|
||||
|
||||
// // Check authentication first
|
||||
// if (!this.isClientAuthenticated(client)) {
|
||||
// throw new WsException({
|
||||
// error: WebSocketMessage.WS_AUTHENTICATION_REQUIRED,
|
||||
// message: WebSocketMessage.WS_AUTHENTICATION_REQUIRED,
|
||||
// statusCode: 401,
|
||||
// });
|
||||
// }
|
||||
|
||||
// // Check room access if roomId is provided in data
|
||||
// if (data?.roomId) {
|
||||
// const user = client.data.user;
|
||||
// const canAccess = this.authService.canAccessRoom(user, data.roomId);
|
||||
|
||||
// if (!canAccess) {
|
||||
// this.logger.warn("WebSocket room access denied", {
|
||||
// clientId: client.id,
|
||||
// userId: user.id,
|
||||
// roomId: data.roomId,
|
||||
// event: data?.event,
|
||||
// });
|
||||
|
||||
// throw new WsException({
|
||||
// error: WebSocketMessage.PERMISSION_DENIED,
|
||||
// message: WebSocketMessage.UNAUTHORIZED_ROOM_ACCESS,
|
||||
// statusCode: 403,
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
||||
// return true;
|
||||
// } catch (error) {
|
||||
// this.logger.error("WebSocket room guard error", {
|
||||
// error: error instanceof Error ? error.message : String(error),
|
||||
// });
|
||||
|
||||
// if (error instanceof WsException) {
|
||||
// throw error;
|
||||
// }
|
||||
|
||||
// throw new WsException({
|
||||
// error: WebSocketMessage.PERMISSION_DENIED,
|
||||
// message: WebSocketMessage.PERMISSION_DENIED,
|
||||
// statusCode: 403,
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
||||
// private isClientAuthenticated(client: Socket): boolean {
|
||||
// return !!(client.data?.user?.id && client.data?.connectedAt);
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,318 @@
|
||||
# Email WebSocket Gateway Usage
|
||||
|
||||
This document explains how to use the WebSocket gateway for real-time email notifications.
|
||||
|
||||
## Overview
|
||||
|
||||
The Email WebSocket Gateway provides real-time notifications for email events such as:
|
||||
|
||||
- New message arrivals
|
||||
- Message status updates (read/unread, flagged/unflagged)
|
||||
- Message movements (archive, favorite, delete)
|
||||
- Connection status updates
|
||||
|
||||
## Connection
|
||||
|
||||
### WebSocket URL
|
||||
|
||||
```
|
||||
ws://localhost:3000/email
|
||||
```
|
||||
|
||||
### Authentication
|
||||
|
||||
The gateway requires JWT authentication. Pass the token in one of these ways:
|
||||
|
||||
**Method 1: Auth object**
|
||||
|
||||
```javascript
|
||||
const socket = io("ws://localhost:3000/email", {
|
||||
auth: {
|
||||
token: "your-jwt-token",
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
**Method 2: Query parameter**
|
||||
|
||||
```javascript
|
||||
const socket = io("ws://localhost:3000/email?token=your-jwt-token");
|
||||
```
|
||||
|
||||
## Client-Side Implementation
|
||||
|
||||
### Basic Setup (JavaScript)
|
||||
|
||||
```javascript
|
||||
import { io } from "socket.io-client";
|
||||
|
||||
const socket = io("ws://localhost:3000/email", {
|
||||
auth: {
|
||||
token: localStorage.getItem("authToken"), // Your JWT token
|
||||
},
|
||||
});
|
||||
|
||||
// Connection events
|
||||
socket.on("connect", () => {
|
||||
console.log("Connected to email gateway");
|
||||
});
|
||||
|
||||
socket.on("connection_established", (data) => {
|
||||
console.log("Connection established:", data);
|
||||
});
|
||||
|
||||
socket.on("connection_error", (error) => {
|
||||
console.error("Connection error:", error);
|
||||
});
|
||||
|
||||
socket.on("disconnect", () => {
|
||||
console.log("Disconnected from email gateway");
|
||||
});
|
||||
```
|
||||
|
||||
### Listening for Email Events
|
||||
|
||||
```javascript
|
||||
// New message notification
|
||||
socket.on("new_message", (message) => {
|
||||
console.log("New message received:", message);
|
||||
// Update UI with new message
|
||||
updateInboxUI(message);
|
||||
showNotification(`New email from ${message.from.name}: ${message.subject}`);
|
||||
});
|
||||
|
||||
// Message status update
|
||||
socket.on("message_status_update", (update) => {
|
||||
console.log("Message status updated:", update);
|
||||
// Update message status in UI
|
||||
updateMessageStatus(update.messageId, update.status);
|
||||
});
|
||||
|
||||
// Message moved
|
||||
socket.on("message_moved", (moveData) => {
|
||||
console.log("Message moved:", moveData);
|
||||
// Update UI to reflect message movement
|
||||
moveMessageInUI(moveData.messageId, moveData.fromMailboxName, moveData.toMailboxName);
|
||||
});
|
||||
|
||||
// Message deleted
|
||||
socket.on("message_deleted", (deleteData) => {
|
||||
console.log("Message deleted:", deleteData);
|
||||
// Remove message from UI
|
||||
removeMessageFromUI(deleteData.messageId);
|
||||
});
|
||||
```
|
||||
|
||||
### Joining/Leaving User Rooms
|
||||
|
||||
```javascript
|
||||
// Join user room (usually done automatically on connection)
|
||||
socket.emit("join_user_room", {
|
||||
userId: "user-id",
|
||||
clientId: socket.id,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
// Leave user room
|
||||
socket.emit("leave_user_room", {
|
||||
userId: "user-id",
|
||||
clientId: socket.id,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
```
|
||||
|
||||
### React Hook Example
|
||||
|
||||
```javascript
|
||||
import { useEffect, useState } from "react";
|
||||
import { io } from "socket.io-client";
|
||||
|
||||
export function useEmailWebSocket(authToken) {
|
||||
const [socket, setSocket] = useState(null);
|
||||
const [isConnected, setIsConnected] = useState(false);
|
||||
const [newMessages, setNewMessages] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!authToken) return;
|
||||
|
||||
const newSocket = io("ws://localhost:3000/email", {
|
||||
auth: { token: authToken },
|
||||
});
|
||||
|
||||
newSocket.on("connect", () => {
|
||||
setIsConnected(true);
|
||||
});
|
||||
|
||||
newSocket.on("disconnect", () => {
|
||||
setIsConnected(false);
|
||||
});
|
||||
|
||||
newSocket.on("new_message", (message) => {
|
||||
setNewMessages((prev) => [...prev, message]);
|
||||
});
|
||||
|
||||
newSocket.on("message_status_update", (update) => {
|
||||
// Handle message status updates
|
||||
console.log("Message status update:", update);
|
||||
});
|
||||
|
||||
setSocket(newSocket);
|
||||
|
||||
return () => {
|
||||
newSocket.disconnect();
|
||||
};
|
||||
}, [authToken]);
|
||||
|
||||
return { socket, isConnected, newMessages };
|
||||
}
|
||||
```
|
||||
|
||||
## Event Types
|
||||
|
||||
### Server to Client Events
|
||||
|
||||
#### `new_message`
|
||||
|
||||
Emitted when a new message arrives.
|
||||
|
||||
```typescript
|
||||
interface EmailNotificationPayload {
|
||||
messageId: number;
|
||||
userId: string;
|
||||
from: { name?: string; address: string };
|
||||
to: Array<{ name?: string; address: string }>;
|
||||
subject: string;
|
||||
preview?: string;
|
||||
hasAttachments: boolean;
|
||||
received: string;
|
||||
mailboxId: string;
|
||||
mailboxName: string;
|
||||
isRead: boolean;
|
||||
isFlagged: boolean;
|
||||
size: number;
|
||||
}
|
||||
```
|
||||
|
||||
#### `message_status_update`
|
||||
|
||||
Emitted when a message's status changes.
|
||||
|
||||
```typescript
|
||||
interface EmailStatusUpdatePayload {
|
||||
messageId: number;
|
||||
userId: string;
|
||||
status: "read" | "unread" | "flagged" | "unflagged" | "archived" | "deleted";
|
||||
mailboxId: string;
|
||||
timestamp: string;
|
||||
}
|
||||
```
|
||||
|
||||
#### `message_moved`
|
||||
|
||||
Emitted when a message is moved between mailboxes.
|
||||
|
||||
```typescript
|
||||
interface EmailMovePayload {
|
||||
messageId: number;
|
||||
userId: string;
|
||||
fromMailboxId: string;
|
||||
toMailboxId: string;
|
||||
fromMailboxName: string;
|
||||
toMailboxName: string;
|
||||
timestamp: string;
|
||||
}
|
||||
```
|
||||
|
||||
#### `message_deleted`
|
||||
|
||||
Emitted when a message is deleted.
|
||||
|
||||
```typescript
|
||||
interface EmailDeletePayload {
|
||||
messageId: number;
|
||||
userId: string;
|
||||
mailboxId: string;
|
||||
mailboxName: string;
|
||||
timestamp: string;
|
||||
}
|
||||
```
|
||||
|
||||
### Client to Server Events
|
||||
|
||||
#### `join_user_room`
|
||||
|
||||
Join a user's room to receive notifications.
|
||||
|
||||
```typescript
|
||||
interface ClientJoinPayload {
|
||||
userId: string;
|
||||
clientId: string;
|
||||
timestamp: string;
|
||||
}
|
||||
```
|
||||
|
||||
#### `leave_user_room`
|
||||
|
||||
Leave a user's room to stop receiving notifications.
|
||||
|
||||
## Error Handling
|
||||
|
||||
```javascript
|
||||
socket.on("connection_error", (error) => {
|
||||
console.error("WebSocket connection error:", error);
|
||||
|
||||
switch (error.error) {
|
||||
case "Authentication required":
|
||||
// Redirect to login
|
||||
break;
|
||||
case "Invalid token":
|
||||
// Refresh token and reconnect
|
||||
break;
|
||||
case "Authentication failed":
|
||||
// Show error message
|
||||
break;
|
||||
default:
|
||||
console.error("Unknown error:", error);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Authentication**: Always include a valid JWT token
|
||||
2. **Reconnection**: Handle connection drops gracefully
|
||||
3. **Error Handling**: Implement proper error handling for all events
|
||||
4. **Memory Management**: Clean up event listeners when components unmount
|
||||
5. **Rate Limiting**: Don't overwhelm the server with too many connections
|
||||
|
||||
## Testing
|
||||
|
||||
You can test the WebSocket gateway using the simulation endpoint:
|
||||
|
||||
```javascript
|
||||
// Call the simulation endpoint from your backend
|
||||
await emailNotificationService.simulateNewMessage("user-id", {
|
||||
subject: "Test Message",
|
||||
from: { name: "Test User", address: "test@example.com" },
|
||||
});
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **Connection Refused**: Check if the server is running and WebSocket dependencies are installed
|
||||
2. **Authentication Failed**: Verify JWT token is valid and not expired
|
||||
3. **No Events Received**: Ensure you're subscribed to the correct user room
|
||||
4. **CORS Issues**: Configure CORS settings in the gateway properly
|
||||
|
||||
### Debug Mode
|
||||
|
||||
Enable debug mode to see detailed logs:
|
||||
|
||||
```javascript
|
||||
const socket = io("ws://localhost:3000/email", {
|
||||
auth: { token: authToken },
|
||||
debug: true,
|
||||
});
|
||||
```
|
||||
@@ -0,0 +1,47 @@
|
||||
export const WEBSOCKET_EVENTS = {
|
||||
// Connection Events
|
||||
CONNECTION_ESTABLISHED: "connection_established",
|
||||
CONNECTION_ERROR: "connection_error",
|
||||
USER_JOINED: "user_joined",
|
||||
USER_LEFT: "user_left",
|
||||
|
||||
// Email Events - Server to Client
|
||||
NEW_MESSAGE: "new_message",
|
||||
MESSAGE_STATUS_UPDATE: "message_status_update",
|
||||
MESSAGE_MOVED: "message_moved",
|
||||
MESSAGE_DELETED: "message_deleted",
|
||||
|
||||
// Room Management - Client to Server
|
||||
JOIN_USER_ROOM: "join_user_room",
|
||||
LEAVE_USER_ROOM: "leave_user_room",
|
||||
JOIN_SESSION: "join_session",
|
||||
LEAVE_SESSION: "leave_session",
|
||||
|
||||
// Authentication Events
|
||||
AUTHENTICATE: "authenticate",
|
||||
AUTHENTICATION_SUCCESS: "authentication_success",
|
||||
AUTHENTICATION_FAILED: "authentication_failed",
|
||||
|
||||
// System Events
|
||||
PING: "ping",
|
||||
PONG: "pong",
|
||||
RECONNECT: "reconnect",
|
||||
FORCE_DISCONNECT: "force_disconnect",
|
||||
} as const;
|
||||
|
||||
export const WEBSOCKET_ROOMS = {
|
||||
USER: (userId: string) => `user_${userId}`,
|
||||
SESSION: (sessionId: string) => `session_${sessionId}`,
|
||||
BROADCAST: "broadcast",
|
||||
} as const;
|
||||
|
||||
export const WEBSOCKET_NAMESPACES = {
|
||||
EMAIL: "/email",
|
||||
NOTIFICATIONS: "/notifications",
|
||||
ADMIN: "/admin",
|
||||
} as const;
|
||||
|
||||
// Type definitions for better TypeScript support
|
||||
export type WebSocketEvent = (typeof WEBSOCKET_EVENTS)[keyof typeof WEBSOCKET_EVENTS];
|
||||
export type WebSocketRoom = string;
|
||||
export type WebSocketNamespace = (typeof WEBSOCKET_NAMESPACES)[keyof typeof WEBSOCKET_NAMESPACES];
|
||||
@@ -0,0 +1,227 @@
|
||||
import { Logger, UseFilters, UseGuards, UsePipes, ValidationPipe } from "@nestjs/common";
|
||||
import {
|
||||
ConnectedSocket,
|
||||
MessageBody,
|
||||
OnGatewayConnection,
|
||||
OnGatewayDisconnect,
|
||||
OnGatewayInit,
|
||||
SubscribeMessage,
|
||||
WebSocketGateway,
|
||||
WebSocketServer,
|
||||
} from "@nestjs/websockets";
|
||||
import { Server, Socket } from "socket.io";
|
||||
|
||||
import { WEBSOCKET_EVENTS } from "./constants/email-events.constant";
|
||||
import {
|
||||
ClientJoinPayload,
|
||||
EmailDeletePayload,
|
||||
EmailMovePayload,
|
||||
EmailNotificationPayload,
|
||||
EmailStatusUpdatePayload,
|
||||
} from "./interfaces/email-events.interface";
|
||||
import { ConnectedUserInfo } from "./interfaces/websocket.interface";
|
||||
import { WebSocketAuthService } from "./services/websocket-auth.service";
|
||||
import { WebSocketMessage } from "../../common/enums/message.enum";
|
||||
import { WsExceptionFilter } from "../../core/filters/ws-exception.filter";
|
||||
import { WsAuthGuard } from "../auth/guards/ws-auth.guard";
|
||||
|
||||
@WebSocketGateway({
|
||||
cors: { origin: "*", credentials: true },
|
||||
namespace: "/email",
|
||||
})
|
||||
@UseFilters(WsExceptionFilter)
|
||||
@UseGuards(WsAuthGuard)
|
||||
@UsePipes(new ValidationPipe())
|
||||
export class EmailGateway implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect {
|
||||
private readonly logger = new Logger(EmailGateway.name);
|
||||
|
||||
// Connection state management
|
||||
private readonly userConnections: Map<string, Set<string>> = new Map(); // userId -> Set of socketIds
|
||||
private readonly connectedUsers = new Map<string, ConnectedUserInfo>();
|
||||
private readonly userSessions = new Map<string, Set<string>>();
|
||||
|
||||
@WebSocketServer()
|
||||
server: Server;
|
||||
constructor(private readonly authService: WebSocketAuthService) {}
|
||||
|
||||
afterInit(server: Server): void {
|
||||
this.logger.log("WebSocket Gateway initialized", {
|
||||
namespace: "/email",
|
||||
cors: true,
|
||||
serverInstance: !!server,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles new WebSocket connections with authentication
|
||||
* Implements fail-fast principle for security
|
||||
*/
|
||||
async handleConnection(client: Socket): Promise<void> {
|
||||
const connectionContext = {
|
||||
clientId: client.id,
|
||||
clientIP: client.handshake.address,
|
||||
userAgent: client.handshake.headers["user-agent"],
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
|
||||
this.logger.log("New WebSocket connection attempt", connectionContext);
|
||||
|
||||
try {
|
||||
const authResult = await this.authService.authenticateClient(client);
|
||||
|
||||
if (!authResult.success) {
|
||||
this.authService.handleAuthenticationFailure(client, authResult.error!);
|
||||
return;
|
||||
}
|
||||
|
||||
const user = authResult.user!;
|
||||
this.registerUserConnection(client.id, user.id);
|
||||
this.authService.emitAuthenticationSuccess(client, user);
|
||||
|
||||
this.logger.log("WebSocket connection established", {
|
||||
...connectionContext,
|
||||
userId: user.id,
|
||||
authenticated: true,
|
||||
});
|
||||
} catch (error) {
|
||||
this.logger.error("WebSocket connection error", {
|
||||
...connectionContext,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
|
||||
this.authService.handleAuthenticationFailure(client, WebSocketMessage.WS_CONNECTION_FAILED);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles client disconnections with cleanup
|
||||
*/
|
||||
handleDisconnect(client: Socket): void {
|
||||
const userInfo = this.connectedUsers.get(client.id);
|
||||
|
||||
this.logger.log("WebSocket client disconnected", {
|
||||
clientId: client.id,
|
||||
userId: userInfo?.userId,
|
||||
sessionId: userInfo?.sessionId,
|
||||
});
|
||||
|
||||
if (userInfo) {
|
||||
this.cleanupUserConnection(client, userInfo);
|
||||
}
|
||||
|
||||
this.connectedUsers.delete(client.id);
|
||||
}
|
||||
|
||||
@SubscribeMessage("join_user_room")
|
||||
async handleJoinUserRoom(@ConnectedSocket() client: Socket, @MessageBody() data: ClientJoinPayload) {
|
||||
try {
|
||||
const userId = client.data.userId;
|
||||
|
||||
if (userId !== data.userId) {
|
||||
client.emit("connection_error", { error: "Unauthorized", timestamp: new Date().toISOString() });
|
||||
return;
|
||||
}
|
||||
|
||||
await client.join(`user:${userId}`);
|
||||
this.logger.log(`Client ${client.id} joined room for user ${userId}`);
|
||||
|
||||
return { success: true, message: "Joined user room successfully" };
|
||||
} catch (error) {
|
||||
this.logger.error(`Error joining user room: ${error instanceof Error ? error.message : "Unknown error"}`);
|
||||
client.emit("connection_error", { error: "Failed to join room", timestamp: new Date().toISOString() });
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeMessage("leave_user_room")
|
||||
async handleLeaveUserRoom(@ConnectedSocket() client: Socket, @MessageBody() data: ClientJoinPayload) {
|
||||
try {
|
||||
const userId = client.data.userId;
|
||||
|
||||
if (userId !== data.userId) {
|
||||
client.emit("connection_error", { error: "Unauthorized", timestamp: new Date().toISOString() });
|
||||
return;
|
||||
}
|
||||
|
||||
await client.leave(`user:${userId}`);
|
||||
this.logger.log(`Client ${client.id} left room for user ${userId}`);
|
||||
|
||||
return { success: true, message: "Left user room successfully" };
|
||||
} catch (error) {
|
||||
this.logger.error(`Error leaving user room: ${error instanceof Error ? error.message : "Unknown error"}`);
|
||||
client.emit("connection_error", { error: "Failed to leave room", timestamp: new Date().toISOString() });
|
||||
}
|
||||
}
|
||||
|
||||
// Methods to emit events to clients
|
||||
async notifyNewMessage(payload: EmailNotificationPayload) {
|
||||
this.logger.log(`Emitting new message notification to user ${payload.userId}`);
|
||||
this.server.to(`user:${payload.userId}`).emit("new_message", payload);
|
||||
}
|
||||
|
||||
async notifyMessageStatusUpdate(payload: EmailStatusUpdatePayload) {
|
||||
this.logger.log(`Emitting message status update to user ${payload.userId}`);
|
||||
this.server.to(`user:${payload.userId}`).emit("message_status_update", payload);
|
||||
}
|
||||
|
||||
async notifyMessageMoved(payload: EmailMovePayload) {
|
||||
this.logger.log(`Emitting message moved notification to user ${payload.userId}`);
|
||||
this.server.to(`user:${payload.userId}`).emit("message_moved", payload);
|
||||
}
|
||||
|
||||
async notifyMessageDeleted(payload: EmailDeletePayload) {
|
||||
this.logger.log(`Emitting message deleted notification to user ${payload.userId}`);
|
||||
this.server.to(`user:${payload.userId}`).emit("message_deleted", payload);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// PRIVATE HELPER METHODS
|
||||
// ============================================================================
|
||||
/**
|
||||
* Cleans up user connection on disconnect
|
||||
*/
|
||||
private cleanupUserConnection(client: Socket, userInfo: ConnectedUserInfo): void {
|
||||
// Remove from user sessions
|
||||
const userSockets = this.userSessions.get(userInfo.userId);
|
||||
if (userSockets) {
|
||||
userSockets.delete(client.id);
|
||||
if (userSockets.size === 0) {
|
||||
this.userSessions.delete(userInfo.userId);
|
||||
}
|
||||
}
|
||||
|
||||
// Leave session room if joined
|
||||
if (userInfo.sessionId) {
|
||||
client.leave(`session_${userInfo.sessionId}`);
|
||||
|
||||
// Notify others in session
|
||||
this.server.to(`session_${userInfo.sessionId}`).emit(WEBSOCKET_EVENTS.USER_LEFT, {
|
||||
userId: userInfo.userId,
|
||||
sessionId: userInfo.sessionId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a new user connection
|
||||
*/
|
||||
private registerUserConnection(clientId: string, userId: string): void {
|
||||
this.connectedUsers.set(clientId, { userId });
|
||||
|
||||
if (!this.userSessions.has(userId)) {
|
||||
this.userSessions.set(userId, new Set());
|
||||
}
|
||||
this.userSessions.get(userId)!.add(clientId);
|
||||
}
|
||||
|
||||
public getUserConnections(userId: string): number {
|
||||
return this.userConnections.get(userId)?.size || 0;
|
||||
}
|
||||
|
||||
public isUserConnected(userId: string): boolean {
|
||||
return this.userConnections.has(userId) && this.userConnections.get(userId)!.size > 0;
|
||||
}
|
||||
|
||||
public getAllConnectedUsers(): string[] {
|
||||
return Array.from(this.userConnections.keys());
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,20 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
import { JwtModule } from "@nestjs/jwt";
|
||||
|
||||
import { EmailController } from "./email.controller";
|
||||
import { EmailGateway } from "./email.gateway";
|
||||
import { EmailNotificationService } from "./services/email-notification.service";
|
||||
import { EmailService } from "./services/email.service";
|
||||
import { MailboxResolverService } from "./services/mailbox-resolver.service";
|
||||
import { WebSocketAuthService } from "./services/websocket-auth.service";
|
||||
import { jwtConfig } from "../../configs/jwt.config";
|
||||
import { MailServerModule } from "../mail-server/mail-server.module";
|
||||
import { UsersModule } from "../users/users.module";
|
||||
|
||||
@Module({
|
||||
imports: [MailServerModule, UsersModule],
|
||||
imports: [MailServerModule, UsersModule, JwtModule.registerAsync(jwtConfig())],
|
||||
controllers: [EmailController],
|
||||
providers: [EmailService, MailboxResolverService],
|
||||
exports: [EmailService, MailboxResolverService],
|
||||
providers: [EmailService, MailboxResolverService, EmailGateway, EmailNotificationService, WebSocketAuthService],
|
||||
exports: [EmailService, MailboxResolverService, EmailGateway, EmailNotificationService, WebSocketAuthService],
|
||||
})
|
||||
export class EmailModule {}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
export interface EmailNotificationPayload {
|
||||
messageId: number;
|
||||
userId: string;
|
||||
from: {
|
||||
name?: string;
|
||||
address: string;
|
||||
};
|
||||
to: Array<{
|
||||
name?: string;
|
||||
address: string;
|
||||
}>;
|
||||
subject: string;
|
||||
preview?: string;
|
||||
hasAttachments: boolean;
|
||||
received: string;
|
||||
mailboxId: string;
|
||||
mailboxName: string;
|
||||
isRead: boolean;
|
||||
isFlagged: boolean;
|
||||
size: number;
|
||||
}
|
||||
|
||||
export interface EmailStatusUpdatePayload {
|
||||
messageId: number;
|
||||
userId: string;
|
||||
status: "read" | "unread" | "flagged" | "unflagged" | "archived" | "deleted";
|
||||
mailboxId: string;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
export interface EmailMovePayload {
|
||||
messageId: number;
|
||||
userId: string;
|
||||
fromMailboxId: string;
|
||||
toMailboxId: string;
|
||||
fromMailboxName: string;
|
||||
toMailboxName: string;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
export interface EmailDeletePayload {
|
||||
messageId: number;
|
||||
userId: string;
|
||||
mailboxId: string;
|
||||
mailboxName: string;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
export interface ClientJoinPayload {
|
||||
userId: string;
|
||||
clientId: string;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
export interface EmailWebSocketEvents {
|
||||
// Client to Server events
|
||||
join_user_room: ClientJoinPayload;
|
||||
leave_user_room: ClientJoinPayload;
|
||||
|
||||
// Server to Client events
|
||||
new_message: EmailNotificationPayload;
|
||||
message_status_update: EmailStatusUpdatePayload;
|
||||
message_moved: EmailMovePayload;
|
||||
message_deleted: EmailDeletePayload;
|
||||
user_joined: ClientJoinPayload;
|
||||
user_left: ClientJoinPayload;
|
||||
connection_established: { userId: string; timestamp: string };
|
||||
connection_error: { error: string; timestamp: string };
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
import { Socket } from "socket.io";
|
||||
|
||||
/**
|
||||
* Information about a connected user
|
||||
*/
|
||||
export interface ConnectedUserInfo {
|
||||
userId: string;
|
||||
sessionId?: string;
|
||||
connectedAt?: Date;
|
||||
lastActivity?: Date;
|
||||
metadata?: Record<string, any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* WebSocket authentication result
|
||||
*/
|
||||
export interface WebSocketAuthResult {
|
||||
success: boolean;
|
||||
user?: WebSocketUser;
|
||||
error?: string;
|
||||
errorCode?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* User information extracted from JWT token
|
||||
*/
|
||||
export interface WebSocketUser {
|
||||
id: string;
|
||||
email?: string;
|
||||
permissions?: string[];
|
||||
isAdmin?: boolean;
|
||||
businessId?: string;
|
||||
sessionId?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* WebSocket connection context for logging and debugging
|
||||
*/
|
||||
export interface WebSocketConnectionContext {
|
||||
clientId: string;
|
||||
clientIP: string;
|
||||
userAgent?: string;
|
||||
timestamp: string;
|
||||
userId?: string;
|
||||
sessionId?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* WebSocket session information
|
||||
*/
|
||||
export interface WebSocketSession {
|
||||
id: string;
|
||||
userId: string;
|
||||
createdAt: Date;
|
||||
lastActivity: Date;
|
||||
clientInfo: {
|
||||
userAgent: string;
|
||||
ip: string;
|
||||
};
|
||||
isActive: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* WebSocket room subscription information
|
||||
*/
|
||||
export interface RoomSubscription {
|
||||
roomId: string;
|
||||
userId: string;
|
||||
subscribedAt: Date;
|
||||
permissions?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* WebSocket error information
|
||||
*/
|
||||
export interface WebSocketError {
|
||||
code: string;
|
||||
message: string;
|
||||
timestamp: string;
|
||||
context?: Record<string, any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* WebSocket authentication payload from client
|
||||
*/
|
||||
export interface WebSocketAuthPayload {
|
||||
token: string;
|
||||
sessionId?: string;
|
||||
clientInfo?: {
|
||||
userAgent?: string;
|
||||
deviceId?: string;
|
||||
platform?: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Extended Socket interface with user data
|
||||
*/
|
||||
export interface AuthenticatedSocket extends Socket {
|
||||
data: {
|
||||
user: WebSocketUser;
|
||||
sessionId?: string;
|
||||
connectedAt: Date;
|
||||
lastActivity: Date;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* WebSocket health check information
|
||||
*/
|
||||
export interface WebSocketHealthInfo {
|
||||
totalConnections: number;
|
||||
activeUsers: number;
|
||||
activeSessions: number;
|
||||
uptime: number;
|
||||
memoryUsage?: {
|
||||
used: number;
|
||||
total: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* WebSocket metrics for monitoring
|
||||
*/
|
||||
export interface WebSocketMetrics {
|
||||
connectionsPerSecond: number;
|
||||
messagesPerSecond: number;
|
||||
errorsPerSecond: number;
|
||||
averageResponseTime: number;
|
||||
activeConnections: number;
|
||||
peakConnections: number;
|
||||
totalMessages: number;
|
||||
totalErrors: number;
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
import { Injectable, Logger } from "@nestjs/common";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { MailServerService } from "../../mail-server/services/mail-server.service";
|
||||
import { EmailGateway } from "../email.gateway";
|
||||
import { MailboxResolverService } from "./mailbox-resolver.service";
|
||||
import { EmailNotificationPayload } from "../interfaces/email-events.interface";
|
||||
|
||||
@Injectable()
|
||||
export class EmailNotificationService {
|
||||
private readonly logger = new Logger(EmailNotificationService.name);
|
||||
|
||||
constructor(
|
||||
private readonly emailGateway: EmailGateway,
|
||||
private readonly mailServerService: MailServerService,
|
||||
private readonly mailboxResolverService: MailboxResolverService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Handle new incoming message and emit WebSocket notification
|
||||
* This method can be called by webhook handlers, polling services, etc.
|
||||
*/
|
||||
async handleNewMessage(userId: string, messageId: number, mailboxId?: string) {
|
||||
try {
|
||||
this.logger.log(`Processing new message notification for user ${userId}, message ${messageId}`);
|
||||
|
||||
// Get the mailbox ID if not provided
|
||||
const targetMailboxId = mailboxId || (await this.mailboxResolverService.getInboxMailboxId(userId));
|
||||
|
||||
// Fetch the message details
|
||||
const message = await firstValueFrom(this.mailServerService.messages.getMessage(userId, targetMailboxId, messageId));
|
||||
|
||||
// Determine mailbox name
|
||||
const mailboxName = await this.getMailboxName(userId, targetMailboxId);
|
||||
|
||||
// Create notification payload
|
||||
const notificationPayload: EmailNotificationPayload = {
|
||||
messageId,
|
||||
userId,
|
||||
from: {
|
||||
name: message.from?.name || message.from?.address?.split("@")[0] || "Unknown",
|
||||
address: message.from?.address || "unknown@example.com",
|
||||
},
|
||||
to:
|
||||
message.to?.map((recipient) => ({
|
||||
name: recipient.name || recipient.address?.split("@")[0] || "Unknown",
|
||||
address: recipient.address || "unknown@example.com",
|
||||
})) || [],
|
||||
subject: message.subject || "No Subject",
|
||||
preview: this.extractPreview(
|
||||
typeof message.text === "string" ? message.text : Array.isArray(message.html) ? message.html.join("") : message.html || "",
|
||||
),
|
||||
hasAttachments: Array.isArray(message.attachments) && message.attachments.length > 0,
|
||||
received: message.idate || new Date().toISOString(),
|
||||
mailboxId: targetMailboxId,
|
||||
mailboxName,
|
||||
isRead: message.seen || false,
|
||||
isFlagged: message.flagged || false,
|
||||
size: message.size || 0,
|
||||
};
|
||||
|
||||
// Emit WebSocket notification
|
||||
await this.emailGateway.notifyNewMessage(notificationPayload);
|
||||
|
||||
this.logger.log(`New message notification sent for user ${userId}, message ${messageId}`);
|
||||
|
||||
return notificationPayload;
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`Failed to process new message notification for user ${userId}, message ${messageId}: ${
|
||||
error instanceof Error ? error.message : "Unknown error"
|
||||
}`,
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle bulk new messages (useful for initial sync or batch processing)
|
||||
*/
|
||||
async handleBulkNewMessages(userId: string, messageIds: number[], mailboxId?: string) {
|
||||
this.logger.log(`Processing bulk new message notifications for user ${userId}, ${messageIds.length} messages`);
|
||||
|
||||
const results = [];
|
||||
for (const messageId of messageIds) {
|
||||
try {
|
||||
const result = await this.handleNewMessage(userId, messageId, mailboxId);
|
||||
results.push(result);
|
||||
} catch (error) {
|
||||
this.logger.error(`Failed to process message ${messageId} in bulk notification: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulate new message arrival (for testing purposes)
|
||||
*/
|
||||
async simulateNewMessage(userId: string, messageData: Partial<EmailNotificationPayload>) {
|
||||
this.logger.log(`Simulating new message for user ${userId}`);
|
||||
|
||||
const simulatedPayload: EmailNotificationPayload = {
|
||||
messageId: Math.floor(Math.random() * 10000),
|
||||
userId,
|
||||
from: {
|
||||
name: "Test Sender",
|
||||
address: "test@example.com",
|
||||
},
|
||||
to: [
|
||||
{
|
||||
name: "Test Recipient",
|
||||
address: "recipient@example.com",
|
||||
},
|
||||
],
|
||||
subject: "Test Message",
|
||||
preview: "This is a test message for WebSocket notification",
|
||||
hasAttachments: false,
|
||||
received: new Date().toISOString(),
|
||||
mailboxId: "inbox",
|
||||
mailboxName: "Inbox",
|
||||
isRead: false,
|
||||
isFlagged: false,
|
||||
size: 1024,
|
||||
...messageData,
|
||||
};
|
||||
|
||||
await this.emailGateway.notifyNewMessage(simulatedPayload);
|
||||
|
||||
return simulatedPayload;
|
||||
}
|
||||
|
||||
private async getMailboxName(userId: string, mailboxId: string): Promise<string> {
|
||||
try {
|
||||
const mailboxIds = await this.mailboxResolverService.getUserMailboxIds(userId);
|
||||
|
||||
// Check which mailbox this ID corresponds to
|
||||
if (mailboxIds.inbox === mailboxId) return "Inbox";
|
||||
if (mailboxIds.sent === mailboxId) return "Sent";
|
||||
if (mailboxIds.drafts === mailboxId) return "Drafts";
|
||||
if (mailboxIds.trash === mailboxId) return "Trash";
|
||||
if (mailboxIds.junk === mailboxId) return "Junk";
|
||||
if (mailboxIds.archive === mailboxId) return "Archive";
|
||||
if (mailboxIds.favorite === mailboxId) return "Favorite";
|
||||
|
||||
return "Unknown";
|
||||
} catch (error) {
|
||||
this.logger.error(`Failed to get mailbox name for ${mailboxId}: ${error}`);
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
private extractPreview(content: string): string {
|
||||
// Remove HTML tags and get first 150 characters
|
||||
const textContent = content.replace(/<[^>]*>/g, "").trim();
|
||||
return textContent.length > 150 ? textContent.substring(0, 150) + "..." : textContent;
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,8 @@ import { EmailMessage } from "../../../common/enums/message.enum";
|
||||
import { MailServerService } from "../../mail-server/services/mail-server.service";
|
||||
import { MessageListQueryDto, SearchMessagesQueryDto } from "../DTO/email-query.dto";
|
||||
import { SendEmailDto, UpdateDraftDto } from "../DTO/send-email.dto";
|
||||
import { EmailGateway } from "../email.gateway";
|
||||
import { EmailDeletePayload, EmailMovePayload, EmailStatusUpdatePayload } from "../interfaces/email-events.interface";
|
||||
|
||||
@Injectable()
|
||||
export class EmailService {
|
||||
@@ -14,6 +16,7 @@ export class EmailService {
|
||||
constructor(
|
||||
private readonly mailServerService: MailServerService,
|
||||
private readonly mailboxResolverService: MailboxResolverService,
|
||||
private readonly emailGateway: EmailGateway,
|
||||
// private readonly userService: UsersService,
|
||||
) {}
|
||||
|
||||
@@ -131,6 +134,16 @@ export class EmailService {
|
||||
const inboxMailboxId = await this.mailboxResolverService.getInboxMailboxId(userId);
|
||||
const result = await firstValueFrom(this.mailServerService.messages.deleteMessage(userId, inboxMailboxId, messageId));
|
||||
|
||||
// Emit WebSocket notification
|
||||
const payload: EmailDeletePayload = {
|
||||
messageId,
|
||||
userId,
|
||||
mailboxId: inboxMailboxId,
|
||||
mailboxName: "Inbox",
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
await this.emailGateway.notifyMessageDeleted(payload);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: EmailMessage.MESSAGE_DELETED_SUCCESSFULLY,
|
||||
@@ -148,6 +161,16 @@ export class EmailService {
|
||||
const inboxMailboxId = await this.mailboxResolverService.getInboxMailboxId(userId);
|
||||
const result = await firstValueFrom(this.mailServerService.messages.updateMessage(userId, inboxMailboxId, messageId, { seen: true }));
|
||||
|
||||
// Emit WebSocket notification
|
||||
const payload: EmailStatusUpdatePayload = {
|
||||
messageId,
|
||||
userId,
|
||||
status: "read",
|
||||
mailboxId: inboxMailboxId,
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
await this.emailGateway.notifyMessageStatusUpdate(payload);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: EmailMessage.MESSAGE_MARKED_AS_SEEN_SUCCESSFULLY,
|
||||
@@ -380,6 +403,18 @@ export class EmailService {
|
||||
}),
|
||||
);
|
||||
|
||||
// Emit WebSocket notification
|
||||
const payload: EmailMovePayload = {
|
||||
messageId,
|
||||
userId,
|
||||
fromMailboxId: inboxMailboxId,
|
||||
toMailboxId: archiveMailboxId,
|
||||
fromMailboxName: "Inbox",
|
||||
toMailboxName: "Archive",
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
await this.emailGateway.notifyMessageMoved(payload);
|
||||
|
||||
this.logger.log(`Message ${messageId} moved to archive successfully`);
|
||||
|
||||
return {
|
||||
@@ -409,6 +444,18 @@ export class EmailService {
|
||||
}),
|
||||
);
|
||||
|
||||
// Emit WebSocket notification
|
||||
const payload: EmailMovePayload = {
|
||||
messageId,
|
||||
userId,
|
||||
fromMailboxId: inboxMailboxId,
|
||||
toMailboxId: favoriteMailboxId,
|
||||
fromMailboxName: "Inbox",
|
||||
toMailboxName: "Favorite",
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
await this.emailGateway.notifyMessageMoved(payload);
|
||||
|
||||
this.logger.log(`Message ${messageId} moved to favorite successfully`);
|
||||
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,240 @@
|
||||
import { Injectable, Logger } from "@nestjs/common";
|
||||
import { JwtService } from "@nestjs/jwt";
|
||||
import { Socket } from "socket.io";
|
||||
|
||||
import { WebSocketMessage } from "../../../common/enums/message.enum";
|
||||
import { WEBSOCKET_EVENTS, WEBSOCKET_ROOMS } from "../constants/email-events.constant";
|
||||
import { AuthenticatedSocket, WebSocketAuthResult, WebSocketConnectionContext, WebSocketUser } from "../interfaces/websocket.interface";
|
||||
|
||||
@Injectable()
|
||||
export class WebSocketAuthService {
|
||||
private readonly logger = new Logger(WebSocketAuthService.name);
|
||||
private readonly connectionTimeouts = new Map<string, NodeJS.Timeout>();
|
||||
|
||||
constructor(private readonly jwtService: JwtService) {}
|
||||
|
||||
/**
|
||||
* Authenticates a WebSocket client using JWT token
|
||||
*/
|
||||
async authenticateClient(client: Socket): Promise<WebSocketAuthResult> {
|
||||
try {
|
||||
const token = this.extractToken(client);
|
||||
|
||||
if (!token) {
|
||||
return {
|
||||
success: false,
|
||||
error: WebSocketMessage.WS_AUTHENTICATION_REQUIRED,
|
||||
};
|
||||
}
|
||||
|
||||
const decoded = await this.verifyToken(token);
|
||||
if (!decoded) {
|
||||
return {
|
||||
success: false,
|
||||
error: WebSocketMessage.WS_INVALID_TOKEN,
|
||||
};
|
||||
}
|
||||
|
||||
const user = this.extractUserFromToken(decoded);
|
||||
if (!user) {
|
||||
return {
|
||||
success: false,
|
||||
error: WebSocketMessage.WS_INVALID_TOKEN,
|
||||
};
|
||||
}
|
||||
|
||||
// Set user data on socket
|
||||
client.data = {
|
||||
user,
|
||||
connectedAt: new Date(),
|
||||
lastActivity: new Date(),
|
||||
sessionId: user.sessionId || this.generateSessionId(),
|
||||
};
|
||||
|
||||
// Join user to their personal room
|
||||
await client.join(WEBSOCKET_ROOMS.USER(user.id));
|
||||
|
||||
return {
|
||||
success: true,
|
||||
user,
|
||||
};
|
||||
} catch (error) {
|
||||
this.logger.error("WebSocket authentication error", {
|
||||
clientId: client.id,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: WebSocketMessage.WS_AUTHENTICATION_FAILED,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles authentication failure by emitting error and disconnecting client
|
||||
*/
|
||||
handleAuthenticationFailure(client: Socket, errorMessage: string): void {
|
||||
const context: WebSocketConnectionContext = {
|
||||
clientId: client.id,
|
||||
clientIP: client.handshake.address,
|
||||
userAgent: client.handshake.headers["user-agent"],
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
|
||||
this.logger.warn("WebSocket authentication failed", {
|
||||
...context,
|
||||
errorMessage,
|
||||
});
|
||||
|
||||
// Emit error to client
|
||||
client.emit(WEBSOCKET_EVENTS.CONNECTION_ERROR, {
|
||||
error: errorMessage,
|
||||
timestamp: new Date().toISOString(),
|
||||
retryAllowed: this.shouldAllowRetry(client),
|
||||
});
|
||||
|
||||
// Set timeout before disconnecting to allow client to handle error
|
||||
const timeout = setTimeout(() => {
|
||||
if (client.connected) {
|
||||
client.disconnect(true);
|
||||
}
|
||||
this.connectionTimeouts.delete(client.id);
|
||||
}, 1000);
|
||||
|
||||
this.connectionTimeouts.set(client.id, timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits successful authentication event to client
|
||||
*/
|
||||
emitAuthenticationSuccess(client: Socket, user: WebSocketUser): void {
|
||||
const authSocket = client as AuthenticatedSocket;
|
||||
|
||||
client.emit(WEBSOCKET_EVENTS.CONNECTION_ESTABLISHED, {
|
||||
userId: user.id,
|
||||
sessionId: authSocket.data.sessionId,
|
||||
timestamp: new Date().toISOString(),
|
||||
permissions: user.permissions || [],
|
||||
});
|
||||
|
||||
this.logger.log("WebSocket authentication successful", {
|
||||
clientId: client.id,
|
||||
userId: user.id,
|
||||
sessionId: authSocket.data.sessionId,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies if user has permission to access a specific room
|
||||
*/
|
||||
canAccessRoom(user: WebSocketUser, roomId: string): boolean {
|
||||
// Basic room access control
|
||||
if (roomId.startsWith("user_")) {
|
||||
const targetUserId = roomId.replace("user_", "");
|
||||
return user.id === targetUserId || user.isAdmin === true;
|
||||
}
|
||||
|
||||
if (roomId.startsWith("session_")) {
|
||||
return true; // Sessions are generally accessible
|
||||
}
|
||||
|
||||
if (roomId === "broadcast") {
|
||||
return user.isAdmin === true;
|
||||
}
|
||||
|
||||
// Default deny for unknown room patterns
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates last activity timestamp for authenticated socket
|
||||
*/
|
||||
updateLastActivity(client: Socket): void {
|
||||
if (client.data?.user) {
|
||||
client.data.lastActivity = new Date();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up connection timeouts on disconnect
|
||||
*/
|
||||
cleanupConnection(client: Socket): void {
|
||||
const timeout = this.connectionTimeouts.get(client.id);
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
this.connectionTimeouts.delete(client.id);
|
||||
}
|
||||
}
|
||||
|
||||
// Private helper methods
|
||||
|
||||
private extractToken(client: Socket): string | null {
|
||||
// Try to get token from auth object first (recommended)
|
||||
const authToken = client.handshake.auth?.token;
|
||||
if (authToken && typeof authToken === "string") {
|
||||
return authToken;
|
||||
}
|
||||
|
||||
// Fallback to query parameter
|
||||
const queryToken = client.handshake.query?.token;
|
||||
if (queryToken && typeof queryToken === "string") {
|
||||
return queryToken;
|
||||
}
|
||||
|
||||
// Try headers as last resort
|
||||
const headerToken = client.handshake.headers.authorization;
|
||||
if (headerToken && typeof headerToken === "string") {
|
||||
return headerToken.replace("Bearer ", "");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private async verifyToken(token: string): Promise<any> {
|
||||
try {
|
||||
return await this.jwtService.verifyAsync(token);
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
if (error.name === "TokenExpiredError") {
|
||||
throw new Error(WebSocketMessage.WS_TOKEN_EXPIRED);
|
||||
}
|
||||
if (error.name === "JsonWebTokenError") {
|
||||
throw new Error(WebSocketMessage.WS_INVALID_TOKEN);
|
||||
}
|
||||
if (error.name === "NotBeforeError") {
|
||||
throw new Error(WebSocketMessage.WS_INVALID_TOKEN);
|
||||
}
|
||||
}
|
||||
throw new Error(WebSocketMessage.WS_INVALID_TOKEN);
|
||||
}
|
||||
}
|
||||
|
||||
private extractUserFromToken(decoded: any): WebSocketUser | null {
|
||||
try {
|
||||
return {
|
||||
id: decoded.wildduckUserId || decoded.sub || decoded.id,
|
||||
email: decoded.email,
|
||||
permissions: decoded.permissions || [],
|
||||
isAdmin: decoded.isAdmin || false,
|
||||
businessId: decoded.businessId,
|
||||
sessionId: decoded.sessionId,
|
||||
};
|
||||
} catch (error) {
|
||||
this.logger.error("Failed to extract user from token", {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private shouldAllowRetry(_client: Socket): boolean {
|
||||
// Implement retry logic based on client IP or other factors
|
||||
// For now, always allow retry
|
||||
return true;
|
||||
}
|
||||
|
||||
private generateSessionId(): string {
|
||||
return `ws_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { Socket } from "socket.io";
|
||||
|
||||
/**
|
||||
* Extracts authentication token from WebSocket client handshake
|
||||
* Supports multiple token sources for flexibility
|
||||
*
|
||||
* @param client - Socket.IO client instance
|
||||
* @returns Authentication token string or undefined if not found
|
||||
*/
|
||||
export function extractTokenFromClient(client: Socket): string | undefined {
|
||||
// Priority order: auth.token > query.token > headers.authorization
|
||||
const authToken = client.handshake?.auth?.token;
|
||||
if (authToken) {
|
||||
return authToken;
|
||||
}
|
||||
|
||||
const queryToken = client.handshake?.query?.token;
|
||||
if (queryToken && typeof queryToken === "string") {
|
||||
return queryToken;
|
||||
}
|
||||
|
||||
const authHeader = client.handshake?.headers?.authorization;
|
||||
if (authHeader && typeof authHeader === "string") {
|
||||
// Remove 'Bearer ' prefix if present
|
||||
return authHeader.replace(/^Bearer\s+/i, "");
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
Reference in New Issue
Block a user