начало чата
All checks were successful
Deploy / deploy (push) Successful in 3m47s

This commit is contained in:
2026-04-16 02:21:54 +06:00
parent 9f39ee6430
commit 0915d3c64d
26 changed files with 1592 additions and 1112 deletions

View File

@@ -10,6 +10,7 @@
"@fastify/autoload": "^6.3.1",
"@fastify/cookie": "^11.0.2",
"@fastify/cors": "^11.1.0",
"@fastify/multipart": "^10.0.0",
"@lucia-auth/adapter-prisma": "^4.0.1",
"@prisma/client": "^6.17.0",
"bcrypt": "^6.0.0",
@@ -21,6 +22,7 @@
"mediasoup": "^3.19.3",
"prisma": "^6.17.0",
"socket.io": "^4.8.1",
"uuid": "^13.0.0",
"ws": "^8.18.3",
"zod": "^4.1.12"
},

View File

@@ -2,6 +2,7 @@ import type { FastifyInstance } from 'fastify'
import type { ServerOptions } from 'socket.io'
import fp from 'fastify-plugin'
import { Server } from 'socket.io'
import registerChatSocket from '../socket/chat.ts'
import registerWebrtcSocket from '../socket/webrtc.ts'
declare module 'fastify' {
@@ -24,6 +25,7 @@ export default fp<Partial<ServerOptions>>(
fastify.ready(async () => {
await registerWebrtcSocket(fastify.io, fastify.mediasoupRouter)
await registerChatSocket(fastify.io)
})
},
{ name: 'socket-io', dependencies: ['mediasoup-worker', 'mediasoup-router'] },

View File

@@ -0,0 +1,33 @@
import type { FastifyInstance } from 'fastify'
import bcrypt from 'bcrypt'
import { z } from 'zod'
export default function (fastify: FastifyInstance) {
fastify.post('/attachments/upload', async (req, reply) => {
try {
const schema = z.object({
file: z.file(),
})
const input = schema.parse(req.body)
// const file = req.file({ limits: { } })
const id = await bcrypt.hash(input.file, 10)
return {
id,
}
}
catch (err) {
fastify.log.error(err)
reply.code(400)
if (err instanceof z.ZodError) {
reply.send({ error: z.prettifyError(err) })
}
else {
reply.send({ error: err.message })
}
}
})
}

View File

@@ -3,9 +3,12 @@ import { fileURLToPath } from 'node:url'
import FastifyAutoLoad from '@fastify/autoload'
import FastifyCookie from '@fastify/cookie'
import FastifyCors from '@fastify/cors'
import FastifyMultipart from '@fastify/multipart'
import Fastify from 'fastify'
import prisma from './prisma/client.ts'
console.log(process.env.DATABASE_URL)
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
@@ -25,6 +28,7 @@ fastify.register(FastifyCors, {
})
fastify.register(FastifyCookie)
fastify.register(FastifyMultipart)
fastify.register(FastifyAutoLoad, {
dir: join(__dirname, 'plugins'),

28
server/socket/chat.ts Normal file
View File

@@ -0,0 +1,28 @@
import type { Server as SocketServer } from 'socket.io'
import type { ChatClientMessage, ChatMessage } from '../types/chat.ts'
import { v4 as uuidv4 } from 'uuid'
export default async function (io: SocketServer) {
const messages: ChatMessage[] = []
io.on('connection', async (socket) => {
socket.on('chat:message', async (clientMessage: ChatClientMessage, cb) => {
const message: ChatMessage = {
id: uuidv4(),
createdAt: new Date().toISOString(),
sender: socket.data.username,
text: clientMessage.text,
}
console.log(message)
messages.push(message)
if (messages.length > 5000) {
messages.shift()
}
io.emit('chat:new-message', message)
})
})
}

View File

@@ -2,7 +2,6 @@ import type { types } from 'mediasoup'
import type { Server as SocketServer } from 'socket.io'
import type {
ChadClient,
Namespace,
SomeSocket,
} from '../types/webrtc.ts'
import { consola } from 'consola'
@@ -10,8 +9,6 @@ import prisma from '../prisma/client.ts'
import { socketToClient } from '../utils/socket-to-client.ts'
export default async function (io: SocketServer, router: types.Router) {
const namespace: Namespace = io.of('/webrtc')
const audioLevelObserver = await router.createAudioLevelObserver({
maxEntries: 10,
threshold: -80,
@@ -21,7 +18,7 @@ export default async function (io: SocketServer, router: types.Router) {
const activeSpeakerObserver = await router.createActiveSpeakerObserver()
audioLevelObserver.on('volumes', async (volumes: types.AudioLevelObserverVolume[]) => {
namespace.emit('speakingPeers', volumes.map(({ producer, volume }) => {
io.emit('speakingPeers', volumes.map(({ producer, volume }) => {
const { socketId } = producer.appData as { socketId: ChadClient['socketId'] }
return {
@@ -32,17 +29,17 @@ export default async function (io: SocketServer, router: types.Router) {
})
audioLevelObserver.on('silence', () => {
namespace.emit('speakingPeers', [])
namespace.emit('activeSpeaker', undefined)
io.emit('speakingPeers', [])
io.emit('activeSpeaker', undefined)
})
activeSpeakerObserver.on('dominantspeaker', ({ producer }) => {
const { socketId } = producer.appData as { socketId: ChadClient['socketId'] }
namespace.emit('activeSpeaker', socketId)
io.emit('activeSpeaker', socketId)
})
namespace.on('connection', async (socket) => {
io.on('connection', async (socket) => {
consola.info('[WebRtc]', 'Client connected', socket.id)
socket.data.joined = false
@@ -350,7 +347,7 @@ export default async function (io: SocketServer, router: types.Router) {
cb(socketToClient(socket))
namespace.emit('clientChanged', socket.id, socketToClient(socket))
io.emit('clientChanged', socket.id, socketToClient(socket))
})
socket.on('disconnect', () => {
@@ -367,7 +364,7 @@ export default async function (io: SocketServer, router: types.Router) {
})
async function getJoinedSockets(excludeId?: string) {
const sockets = await namespace.fetchSockets()
const sockets = await io.fetchSockets()
return sockets.filter(socket => socket.data.joined && (excludeId ? excludeId !== socket.id : true))
}

18
server/types/chat.ts Normal file
View File

@@ -0,0 +1,18 @@
export interface ChatClientMessage {
text: string
replyTo?: {
messageId: string
}
}
export interface ChatMessage {
id: string
sender: string
text: string
createdAt: string
replyTo?: {
messageId: string
sender: string
text: string
}
}

View File

@@ -331,6 +331,13 @@ __metadata:
languageName: node
linkType: hard
"@fastify/busboy@npm:^3.0.0":
version: 3.2.0
resolution: "@fastify/busboy@npm:3.2.0"
checksum: 10c0/3e4fb00a27e3149d1c68de8ff14007d2bbcbbc171a9d050d0a8772e836727329d4d3f130995ebaa19cf537d5d2f5ce2a88000366e6192e751457bfcc2125f351
languageName: node
linkType: hard
"@fastify/cookie@npm:^11.0.2":
version: 11.0.2
resolution: "@fastify/cookie@npm:11.0.2"
@@ -351,6 +358,13 @@ __metadata:
languageName: node
linkType: hard
"@fastify/deepmerge@npm:^3.0.0":
version: 3.2.1
resolution: "@fastify/deepmerge@npm:3.2.1"
checksum: 10c0/2c0f8b627537834822ec761842e3a57965d3fb59e011ac5f2215b618ce34698e277bcbe4f586b09e4f03347391f292cfef9dccb6bda7b019ea9420d8767ade49
languageName: node
linkType: hard
"@fastify/error@npm:^4.0.0":
version: 4.2.0
resolution: "@fastify/error@npm:4.2.0"
@@ -383,6 +397,19 @@ __metadata:
languageName: node
linkType: hard
"@fastify/multipart@npm:^10.0.0":
version: 10.0.0
resolution: "@fastify/multipart@npm:10.0.0"
dependencies:
"@fastify/busboy": "npm:^3.0.0"
"@fastify/deepmerge": "npm:^3.0.0"
"@fastify/error": "npm:^4.0.0"
fastify-plugin: "npm:^5.0.0"
secure-json-parse: "npm:^4.0.0"
checksum: 10c0/49e09135599a59aab761b71f65ab5f9ad63c3ea28ede04389c308dea1149b1fd7779693230f5bc0ffc9063c42484e99a2d1d38c0fd73b074c9ed3216db12e9da
languageName: node
linkType: hard
"@fastify/proxy-addr@npm:^5.0.0":
version: 5.1.0
resolution: "@fastify/proxy-addr@npm:5.1.0"
@@ -4265,6 +4292,7 @@ __metadata:
"@fastify/autoload": "npm:^6.3.1"
"@fastify/cookie": "npm:^11.0.2"
"@fastify/cors": "npm:^11.1.0"
"@fastify/multipart": "npm:^10.0.0"
"@lucia-auth/adapter-prisma": "npm:^4.0.1"
"@prisma/client": "npm:^6.17.0"
"@types/bcrypt": "npm:^6"
@@ -4281,6 +4309,7 @@ __metadata:
socket.io: "npm:^4.8.1"
ts-node: "npm:^10.9.2"
typescript: "npm:^5.9.3"
uuid: "npm:^13.0.0"
ws: "npm:^8.18.3"
zod: "npm:^4.1.12"
languageName: unknown
@@ -4787,6 +4816,15 @@ __metadata:
languageName: node
linkType: hard
"uuid@npm:^13.0.0":
version: 13.0.0
resolution: "uuid@npm:13.0.0"
bin:
uuid: dist-node/bin/uuid
checksum: 10c0/950e4c18d57fef6c69675344f5700a08af21e26b9eff2bf2180427564297368c538ea11ac9fb2e6528b17fc3966a9fd2c5049361b0b63c7d654f3c550c9b3d67
languageName: node
linkType: hard
"v8-compile-cache-lib@npm:^3.0.1":
version: 3.0.1
resolution: "v8-compile-cache-lib@npm:3.0.1"