replace websocket with sse
This commit is contained in:
parent
2e015c3f5e
commit
de67f784a3
22
package-lock.json
generated
22
package-lock.json
generated
@ -38,7 +38,6 @@
|
|||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"vanilla-calendar-pro": "^3.0.4",
|
"vanilla-calendar-pro": "^3.0.4",
|
||||||
"ws": "^8.18.1",
|
|
||||||
"zustand": "^5.0.3"
|
"zustand": "^5.0.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -7503,27 +7502,6 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ws": {
|
|
||||||
"version": "8.18.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz",
|
|
||||||
"integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==",
|
|
||||||
"license": "MIT",
|
|
||||||
"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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/yallist": {
|
"node_modules/yallist": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||||
|
|||||||
@ -41,7 +41,6 @@
|
|||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"vanilla-calendar-pro": "^3.0.4",
|
"vanilla-calendar-pro": "^3.0.4",
|
||||||
"ws": "^8.18.1",
|
|
||||||
"zustand": "^5.0.3"
|
"zustand": "^5.0.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
// /api/team/create/route.ts
|
// /api/team/create/route.ts
|
||||||
import { NextResponse, type NextRequest } from 'next/server'
|
import { NextResponse, type NextRequest } from 'next/server'
|
||||||
import { prisma } from '@/app/lib/prisma';
|
import { prisma } from '@/app/lib/prisma';
|
||||||
import { sendServerWebSocketMessage } from '@/app/lib/websocket-server-client';
|
import { sendServerSSEMessage } from '@/app/lib/sse-server-client';
|
||||||
|
|
||||||
export async function POST(req: NextRequest) {
|
export async function POST(req: NextRequest) {
|
||||||
try {
|
try {
|
||||||
@ -37,14 +37,14 @@ export async function POST(req: NextRequest) {
|
|||||||
|
|
||||||
await prisma.notification.create({
|
await prisma.notification.create({
|
||||||
data: {
|
data: {
|
||||||
userId: leader.steamId,
|
steamId: leader.steamId,
|
||||||
title: 'Team erstellt',
|
title: 'Team erstellt',
|
||||||
message: `Du hast erfolgreich das Team "${teamname}" erstellt.`,
|
message: `Du hast erfolgreich das Team "${teamname}" erstellt.`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// 📢 WebSocket Nachricht senden
|
// 📢 SSE Nachricht senden
|
||||||
await sendServerWebSocketMessage({
|
await sendServerSSEMessage({
|
||||||
type: 'team-created',
|
type: 'team-created',
|
||||||
title: 'Team erstellt',
|
title: 'Team erstellt',
|
||||||
message: `Das Team "${teamname}" wurde erstellt.`,
|
message: `Das Team "${teamname}" wurde erstellt.`,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { NextResponse, type NextRequest } from 'next/server'
|
import { NextResponse, type NextRequest } from 'next/server'
|
||||||
import { prisma } from '@/app/lib/prisma'
|
import { prisma } from '@/app/lib/prisma'
|
||||||
import { sendServerWebSocketMessage } from '@/app/lib/websocket-server-client'
|
import { sendServerSSEMessage } from '@/app/lib/sse-server-client'
|
||||||
|
|
||||||
export async function POST(req: NextRequest) {
|
export async function POST(req: NextRequest) {
|
||||||
try {
|
try {
|
||||||
@ -45,7 +45,7 @@ export async function POST(req: NextRequest) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
await sendServerWebSocketMessage({
|
await sendServerSSEMessage({
|
||||||
type: notification.actionType ?? 'notification',
|
type: notification.actionType ?? 'notification',
|
||||||
targetUserIds: [userId],
|
targetUserIds: [userId],
|
||||||
message: notification.message,
|
message: notification.message,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { NextResponse, type NextRequest } from 'next/server'
|
import { NextResponse, type NextRequest } from 'next/server'
|
||||||
import { prisma } from '@/app/lib/prisma'
|
import { prisma } from '@/app/lib/prisma'
|
||||||
import { sendServerWebSocketMessage } from '@/app/lib/websocket-server-client'
|
import { sendServerSSEMessage } from '@/app/lib/sse-server-client'
|
||||||
|
|
||||||
export const dynamic = 'force-dynamic'
|
export const dynamic = 'force-dynamic'
|
||||||
|
|
||||||
@ -52,7 +52,7 @@ export async function POST(req: NextRequest) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
await sendServerWebSocketMessage({
|
await sendServerSSEMessage({
|
||||||
type: notification.actionType ?? 'notification',
|
type: notification.actionType ?? 'notification',
|
||||||
targetUserIds: [steamId],
|
targetUserIds: [steamId],
|
||||||
message: notification.message,
|
message: notification.message,
|
||||||
@ -76,7 +76,7 @@ export async function POST(req: NextRequest) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
await sendServerWebSocketMessage({
|
await sendServerSSEMessage({
|
||||||
type: notification.actionType ?? 'notification',
|
type: notification.actionType ?? 'notification',
|
||||||
targetUserIds: [userId],
|
targetUserIds: [userId],
|
||||||
message: notification.message,
|
message: notification.message,
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { NextResponse, type NextRequest } from 'next/server'
|
import { NextResponse, type NextRequest } from 'next/server'
|
||||||
import { prisma } from '@/app/lib/prisma'
|
import { prisma } from '@/app/lib/prisma'
|
||||||
import { removePlayerFromTeam } from '@/app/lib/removePlayerFromTeam'
|
import { removePlayerFromTeam } from '@/app/lib/removePlayerFromTeam'
|
||||||
import { sendServerWebSocketMessage } from '@/app/lib/websocket-server-client'
|
import { sendServerSSEMessage } from '@/app/lib/sse-server-client'
|
||||||
|
|
||||||
export async function POST(req: NextRequest) {
|
export async function POST(req: NextRequest) {
|
||||||
try {
|
try {
|
||||||
@ -66,7 +66,7 @@ export async function POST(req: NextRequest) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
await sendServerWebSocketMessage({
|
await sendServerSSEMessage({
|
||||||
type: notification.actionType ?? 'notification',
|
type: notification.actionType ?? 'notification',
|
||||||
targetUserIds: [steamId],
|
targetUserIds: [steamId],
|
||||||
message: notification.message,
|
message: notification.message,
|
||||||
@ -93,7 +93,7 @@ export async function POST(req: NextRequest) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
await sendServerWebSocketMessage({
|
await sendServerSSEMessage({
|
||||||
type: notification.actionType ?? 'notification',
|
type: notification.actionType ?? 'notification',
|
||||||
targetUserIds: [userId],
|
targetUserIds: [userId],
|
||||||
message: notification.message,
|
message: notification.message,
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
// /app/api/team/rename/route.ts
|
// /app/api/team/rename/route.ts
|
||||||
import { NextResponse, type NextRequest } from 'next/server'
|
import { NextResponse, type NextRequest } from 'next/server'
|
||||||
import { prisma } from '@/app/lib/prisma'
|
import { prisma } from '@/app/lib/prisma'
|
||||||
import { sendServerWebSocketMessage } from '@/app/lib/websocket-server-client'
|
import { sendServerSSEMessage } from '@/app/lib/sse-server-client'
|
||||||
|
|
||||||
export async function POST(req: NextRequest) {
|
export async function POST(req: NextRequest) {
|
||||||
try {
|
try {
|
||||||
@ -16,8 +16,8 @@ export async function POST(req: NextRequest) {
|
|||||||
data: { name: newName },
|
data: { name: newName },
|
||||||
})
|
})
|
||||||
|
|
||||||
// 🔔 WebSocket Nachricht an alle User (global)
|
// 🔔 SSE Nachricht an alle User (global)
|
||||||
await sendServerWebSocketMessage({
|
await sendServerSSEMessage({
|
||||||
type: 'team-renamed',
|
type: 'team-renamed',
|
||||||
title: 'Team umbenannt!',
|
title: 'Team umbenannt!',
|
||||||
message: `Das Team wurde umbenannt in "${newName}".`,
|
message: `Das Team wurde umbenannt in "${newName}".`,
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { NextResponse, type NextRequest } from 'next/server'
|
|||||||
import { prisma } from '@/app/lib/prisma'
|
import { prisma } from '@/app/lib/prisma'
|
||||||
import { getServerSession } from 'next-auth'
|
import { getServerSession } from 'next-auth'
|
||||||
import { authOptions } from '@/app/lib/auth'
|
import { authOptions } from '@/app/lib/auth'
|
||||||
import { sendServerWebSocketMessage } from '@/app/lib/websocket-server-client'
|
import { sendServerSSEMessage } from '@/app/lib/sse-server-client'
|
||||||
|
|
||||||
export async function POST(req: NextRequest) {
|
export async function POST(req: NextRequest) {
|
||||||
try {
|
try {
|
||||||
@ -37,7 +37,7 @@ export async function POST(req: NextRequest) {
|
|||||||
|
|
||||||
/* ---- Doppelte Anfrage vermeiden ------------------------------ */
|
/* ---- Doppelte Anfrage vermeiden ------------------------------ */
|
||||||
const existingInvite = await prisma.teamInvite.findFirst({
|
const existingInvite = await prisma.teamInvite.findFirst({
|
||||||
where: { userId: requesterSteamId, teamId },
|
where: { steamId: requesterSteamId, teamId },
|
||||||
})
|
})
|
||||||
if (existingInvite) {
|
if (existingInvite) {
|
||||||
return NextResponse.json({ message: 'Anfrage läuft bereits' }, { status: 200 })
|
return NextResponse.json({ message: 'Anfrage läuft bereits' }, { status: 200 })
|
||||||
@ -46,7 +46,7 @@ export async function POST(req: NextRequest) {
|
|||||||
/* ---- Invitation anlegen -------------------------------------- */
|
/* ---- Invitation anlegen -------------------------------------- */
|
||||||
await prisma.teamInvite.create({
|
await prisma.teamInvite.create({
|
||||||
data: {
|
data: {
|
||||||
userId: requesterSteamId, // User.steamId
|
steamId: requesterSteamId, // User.steamId
|
||||||
teamId,
|
teamId,
|
||||||
type: 'team-join-request',
|
type: 'team-join-request',
|
||||||
},
|
},
|
||||||
@ -55,7 +55,7 @@ export async function POST(req: NextRequest) {
|
|||||||
/* ---- Leader benachrichtigen ---------------------------------- */
|
/* ---- Leader benachrichtigen ---------------------------------- */
|
||||||
const notification = await prisma.notification.create({
|
const notification = await prisma.notification.create({
|
||||||
data: {
|
data: {
|
||||||
userId: team.leaderId!,
|
steamId: team.leaderId!,
|
||||||
title: 'Beitrittsanfrage',
|
title: 'Beitrittsanfrage',
|
||||||
message: `${session.user.name ?? 'Ein Spieler'} möchte deinem Team beitreten.`,
|
message: `${session.user.name ?? 'Ein Spieler'} möchte deinem Team beitreten.`,
|
||||||
actionType: 'team-join-request',
|
actionType: 'team-join-request',
|
||||||
@ -63,8 +63,8 @@ export async function POST(req: NextRequest) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
/* ---- WebSocket Event (optional) ------------------------------ */
|
/* ---- SSE Event (optional) ------------------------------ */
|
||||||
await sendServerWebSocketMessage({
|
await sendServerSSEMessage({
|
||||||
type: notification.actionType ?? 'notification',
|
type: notification.actionType ?? 'notification',
|
||||||
targetUserIds: [team.leaderId],
|
targetUserIds: [team.leaderId],
|
||||||
message: notification.message,
|
message: notification.message,
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { prisma } from '@/app/lib/prisma'
|
import { prisma } from '@/app/lib/prisma'
|
||||||
import { NextResponse, type NextRequest } from 'next/server'
|
import { NextResponse, type NextRequest } from 'next/server'
|
||||||
import { sendServerWebSocketMessage } from '@/app/lib/websocket-server-client'
|
import { sendServerSSEMessage } from '@/app/lib/sse-server-client'
|
||||||
|
|
||||||
export async function POST(req: NextRequest) {
|
export async function POST(req: NextRequest) {
|
||||||
try {
|
try {
|
||||||
@ -39,7 +39,7 @@ export async function POST(req: NextRequest) {
|
|||||||
select: { name: true },
|
select: { name: true },
|
||||||
})
|
})
|
||||||
|
|
||||||
await sendServerWebSocketMessage({
|
await sendServerSSEMessage({
|
||||||
type: 'team-leader-changed',
|
type: 'team-leader-changed',
|
||||||
title: 'Neuer Teamleader',
|
title: 'Neuer Teamleader',
|
||||||
message: `${newLeader?.name ?? 'Ein Spieler'} ist jetzt Teamleader.`,
|
message: `${newLeader?.name ?? 'Ein Spieler'} ist jetzt Teamleader.`,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
// ✅ /api/team/update-players/route.ts
|
// ✅ /api/team/update-players/route.ts
|
||||||
import { prisma } from '@/app/lib/prisma'
|
import { prisma } from '@/app/lib/prisma'
|
||||||
import { sendServerWebSocketMessage } from '@/app/lib/websocket-server-client'
|
import { sendServerSSEMessage } from '@/app/lib/sse-server-client'
|
||||||
import { NextResponse, type NextRequest } from 'next/server'
|
import { NextResponse, type NextRequest } from 'next/server'
|
||||||
|
|
||||||
export async function POST(req: NextRequest) {
|
export async function POST(req: NextRequest) {
|
||||||
@ -18,7 +18,7 @@ export async function POST(req: NextRequest) {
|
|||||||
|
|
||||||
const allSteamIds = [...activePlayers, ...inactivePlayers]
|
const allSteamIds = [...activePlayers, ...inactivePlayers]
|
||||||
|
|
||||||
await sendServerWebSocketMessage({
|
await sendServerSSEMessage({
|
||||||
type: 'team-updated',
|
type: 'team-updated',
|
||||||
teamId,
|
teamId,
|
||||||
targetUserIds: allSteamIds,
|
targetUserIds: allSteamIds,
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { NextResponse, type NextRequest } from 'next/server'
|
|||||||
import { writeFile, mkdir, unlink } from 'fs/promises'
|
import { writeFile, mkdir, unlink } from 'fs/promises'
|
||||||
import { join, dirname } from 'path'
|
import { join, dirname } from 'path'
|
||||||
import { randomUUID } from 'crypto'
|
import { randomUUID } from 'crypto'
|
||||||
import { sendServerWebSocketMessage } from '@/app/lib/websocket-server-client'
|
import { sendServerSSEMessage } from '@/app/lib/sse-server-client'
|
||||||
|
|
||||||
export async function POST(req: NextRequest) {
|
export async function POST(req: NextRequest) {
|
||||||
const formData = await req.formData()
|
const formData = await req.formData()
|
||||||
@ -47,7 +47,7 @@ export async function POST(req: NextRequest) {
|
|||||||
data: { logo: filename },
|
data: { logo: filename },
|
||||||
})
|
})
|
||||||
|
|
||||||
await sendServerWebSocketMessage({
|
await sendServerSSEMessage({
|
||||||
type: 'team-logo-updated',
|
type: 'team-logo-updated',
|
||||||
title: 'Team-Logo hochgeladen!',
|
title: 'Team-Logo hochgeladen!',
|
||||||
message: `Das Teamlogo wurde aktualisiert.`,
|
message: `Das Teamlogo wurde aktualisiert.`,
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
// /api/user/invitations/[action]/route.ts
|
// /api/user/invitations/[action]/route.ts
|
||||||
import { NextResponse, type NextRequest } from 'next/server'
|
import { NextResponse, type NextRequest } from 'next/server'
|
||||||
import { prisma } from '@/app/lib/prisma'
|
import { prisma } from '@/app/lib/prisma'
|
||||||
import { sendServerWebSocketMessage } from '@/app/lib/websocket-server-client'
|
import { sendServerSSEMessage } from '@/app/lib/sse-server-client'
|
||||||
|
|
||||||
export const dynamic = 'force-dynamic'
|
export const dynamic = 'force-dynamic'
|
||||||
|
|
||||||
@ -58,7 +58,7 @@ export async function POST(
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
await sendServerWebSocketMessage({
|
await sendServerSSEMessage({
|
||||||
type: notification.actionType ?? 'notification',
|
type: notification.actionType ?? 'notification',
|
||||||
targetUserIds: [steamId],
|
targetUserIds: [steamId],
|
||||||
message: notification.message,
|
message: notification.message,
|
||||||
@ -86,7 +86,7 @@ export async function POST(
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
await sendServerWebSocketMessage({
|
await sendServerSSEMessage({
|
||||||
type: notification.actionType ?? 'notification',
|
type: notification.actionType ?? 'notification',
|
||||||
targetUserIds: [otherUserId],
|
targetUserIds: [otherUserId],
|
||||||
message: notification.message,
|
message: notification.message,
|
||||||
@ -118,7 +118,7 @@ export async function POST(
|
|||||||
? 'team-join-request-reject'
|
? 'team-join-request-reject'
|
||||||
: 'team-invite-reject'
|
: 'team-invite-reject'
|
||||||
|
|
||||||
await sendServerWebSocketMessage({
|
await sendServerSSEMessage({
|
||||||
type: eventType,
|
type: eventType,
|
||||||
targetUserIds: [steamId],
|
targetUserIds: [steamId],
|
||||||
message: `Einladung zu Team "${team?.name}" wurde abgelehnt.`,
|
message: `Einladung zu Team "${team?.name}" wurde abgelehnt.`,
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { ReactNode, forwardRef, useState, useRef, useEffect } from 'react'
|
|||||||
type ButtonProps = {
|
type ButtonProps = {
|
||||||
title?: string
|
title?: string
|
||||||
children?: ReactNode
|
children?: ReactNode
|
||||||
onClick?: () => void
|
onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void
|
||||||
onToggle?: (open: boolean) => void
|
onToggle?: (open: boolean) => void
|
||||||
modalId?: string
|
modalId?: string
|
||||||
color?: 'blue' | 'red' | 'gray' | 'green' | 'teal' | 'transparent'
|
color?: 'blue' | 'red' | 'gray' | 'green' | 'teal' | 'transparent'
|
||||||
@ -13,6 +13,7 @@ type ButtonProps = {
|
|||||||
size?: 'sm' | 'md' | 'lg'
|
size?: 'sm' | 'md' | 'lg'
|
||||||
className?: string
|
className?: string
|
||||||
dropDirection?: "up" | "down" | "auto"
|
dropDirection?: "up" | "down" | "auto"
|
||||||
|
disabled?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
|
const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
|
||||||
@ -26,7 +27,8 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
|
|||||||
variant = 'solid',
|
variant = 'solid',
|
||||||
size = 'md',
|
size = 'md',
|
||||||
className,
|
className,
|
||||||
dropDirection = "down"
|
dropDirection = "down",
|
||||||
|
disabled = false
|
||||||
},
|
},
|
||||||
ref
|
ref
|
||||||
) {
|
) {
|
||||||
@ -130,12 +132,12 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
|
|||||||
}
|
}
|
||||||
}, [open, dropDirection]);
|
}, [open, dropDirection]);
|
||||||
|
|
||||||
const toggle = () => {
|
const toggle = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
const next = !open
|
const next = !open
|
||||||
setOpen(next)
|
setOpen(next)
|
||||||
onToggle?.(next)
|
onToggle?.(next)
|
||||||
onClick?.()
|
onClick?.(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
|
|||||||
@ -45,12 +45,15 @@ export default function CompRankBadge({ rank }: Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip content={altText}>
|
<Tooltip content={altText}>
|
||||||
|
<div style={{ position: 'relative', width: 70, height: 40 }}>
|
||||||
<Image
|
<Image
|
||||||
src={`/assets/img/skillgroups/${imageName}`}
|
src={`/assets/img/skillgroups/${imageName}`}
|
||||||
alt={altText}
|
alt={altText}
|
||||||
width={60}
|
fill
|
||||||
height={60}
|
style={{ objectFit: 'contain' }}
|
||||||
|
sizes="(max-width: 768px) 100px, 70px"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import NotificationDropdown from './NotificationDropdown'
|
import NotificationDropdown from './NotificationDropdown'
|
||||||
import { useWS } from '@/app/lib/wsStore'
|
import { useSSE } from '@/app/lib/useSSEStore'
|
||||||
import { useSession } from 'next-auth/react'
|
import { useSession } from 'next-auth/react'
|
||||||
import { useTeamManager } from '../hooks/useTeamManager'
|
import { useTeamManager } from '../hooks/useTeamManager'
|
||||||
import { useRouter } from 'next/navigation'
|
import { useRouter } from 'next/navigation'
|
||||||
@ -21,7 +21,7 @@ export default function NotificationCenter() {
|
|||||||
const { data: session } = useSession()
|
const { data: session } = useSession()
|
||||||
const [notifications, setNotifications] = useState<Notification[]>([])
|
const [notifications, setNotifications] = useState<Notification[]>([])
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
const { socket, connect } = useWS()
|
const { source, connect } = useSSE()
|
||||||
const { markAllAsRead, markOneAsRead, handleInviteAction } = useTeamManager({}, null)
|
const { markAllAsRead, markOneAsRead, handleInviteAction } = useTeamManager({}, null)
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const [previewText, setPreviewText] = useState<string | null>(null)
|
const [previewText, setPreviewText] = useState<string | null>(null)
|
||||||
@ -70,9 +70,10 @@ export default function NotificationCenter() {
|
|||||||
}, [session?.user?.steamId, connect])
|
}, [session?.user?.steamId, connect])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!socket) return
|
if (!source) return
|
||||||
|
|
||||||
const handleMessage = (event: MessageEvent) => {
|
const handleEvent = (event: MessageEvent) => {
|
||||||
|
try {
|
||||||
const data = JSON.parse(event.data)
|
const data = JSON.parse(event.data)
|
||||||
if (data.type === 'heartbeat') return
|
if (data.type === 'heartbeat') return
|
||||||
|
|
||||||
@ -109,18 +110,17 @@ export default function NotificationCenter() {
|
|||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setShowPreview(false)
|
setShowPreview(false)
|
||||||
setTimeout(() => {
|
setTimeout(() => setPreviewText(null), 300)
|
||||||
setPreviewText(null)
|
|
||||||
}, 300)
|
|
||||||
setAnimateBell(false)
|
setAnimateBell(false)
|
||||||
}, 3000)
|
}, 3000)
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[SSE] Ungültige Nachricht:', event)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
socket.addEventListener('message', handleMessage)
|
source.addEventListener('notification', handleEvent)
|
||||||
return () => socket.removeEventListener('message', handleMessage)
|
return () => source.removeEventListener('notification', handleEvent)
|
||||||
}, [socket])
|
}, [source])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed bottom-6 right-6 z-50">
|
<div className="fixed bottom-6 right-6 z-50">
|
||||||
|
|||||||
@ -2,26 +2,24 @@
|
|||||||
|
|
||||||
import { useSession } from 'next-auth/react'
|
import { useSession } from 'next-auth/react'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { useWS } from '@/app/lib/wsStore'
|
import { useSSE } from '@/app/lib/useSSEStore'
|
||||||
|
|
||||||
export default function WebSocketManager() {
|
export default function SSEManager() {
|
||||||
const { data: session } = useSession()
|
const { data: session } = useSession()
|
||||||
const connectWS = useWS((s) => s.connect)
|
const connect = useSSE((s) => s.connect)
|
||||||
const disconnectWS = useWS((s) => s.disconnect)
|
const disconnect = useSSE((s) => s.disconnect)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!session?.user?.steamId) return
|
const steamId = session?.user?.steamId
|
||||||
|
if (!steamId) return
|
||||||
|
|
||||||
connectWS(session.user.steamId)
|
const eventSource = connect(steamId)
|
||||||
|
if (!eventSource) return
|
||||||
|
|
||||||
const socket = useWS.getState().socket
|
eventSource.onmessage = (event) => {
|
||||||
if (!socket) return
|
|
||||||
|
|
||||||
socket.onmessage = (event) => {
|
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(event.data)
|
const data = JSON.parse(event.data)
|
||||||
|
|
||||||
// Typbasierter Event-Dispatch
|
|
||||||
switch (data.type) {
|
switch (data.type) {
|
||||||
case 'invitation':
|
case 'invitation':
|
||||||
window.dispatchEvent(new CustomEvent('ws-invitation'))
|
window.dispatchEvent(new CustomEvent('ws-invitation'))
|
||||||
@ -60,23 +58,22 @@ export default function WebSocketManager() {
|
|||||||
window.dispatchEvent(new CustomEvent('ws-team-join-request'))
|
window.dispatchEvent(new CustomEvent('ws-team-join-request'))
|
||||||
break
|
break
|
||||||
case 'team-renamed':
|
case 'team-renamed':
|
||||||
console.log('[WS] team-renamed', data.teamId)
|
|
||||||
window.dispatchEvent(new CustomEvent('ws-team-renamed', {
|
window.dispatchEvent(new CustomEvent('ws-team-renamed', {
|
||||||
detail: { teamId: data.teamId }
|
detail: { teamId: data.teamId }
|
||||||
}))
|
}))
|
||||||
break
|
break
|
||||||
case 'team-logo-updated':
|
case 'team-logo-updated':
|
||||||
window.dispatchEvent(new CustomEvent('ws-team-logo-updated', { detail: { teamId: data.teamId } }))
|
window.dispatchEvent(new CustomEvent('ws-team-logo-updated', {
|
||||||
|
detail: { teamId: data.teamId }
|
||||||
|
}))
|
||||||
break
|
break
|
||||||
|
|
||||||
// Weitere Events hier hinzufügen ...
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (err) {
|
||||||
console.error('[WebSocket] Ungültige Nachricht:', event.data)
|
console.error('[SSE] Ungültige Nachricht:', event.data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => disconnectWS()
|
return () => disconnect()
|
||||||
}, [session?.user?.steamId])
|
}, [session?.user?.steamId])
|
||||||
|
|
||||||
return null
|
return null
|
||||||
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import Button from './Button'
|
import Button from './Button'
|
||||||
import { useWebSocketListener } from '@/app/hooks/useWebSocketListener'
|
|
||||||
import { Team, Player } from '../types/team'
|
import { Team, Player } from '../types/team'
|
||||||
import { useLiveTeam } from '../hooks/useLiveTeam'
|
import { useLiveTeam } from '../hooks/useLiveTeam'
|
||||||
|
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import InvitePlayersModal from './InvitePlayersModal'
|
|||||||
import Modal from './Modal'
|
import Modal from './Modal'
|
||||||
import { Player, Team } from '../types/team'
|
import { Player, Team } from '../types/team'
|
||||||
import { useSession } from 'next-auth/react'
|
import { useSession } from 'next-auth/react'
|
||||||
import { useWS } from '@/app/lib/wsStore'
|
import { useWS } from '@/app/lib/useSSEStore'
|
||||||
import { AnimatePresence, motion } from 'framer-motion'
|
import { AnimatePresence, motion } from 'framer-motion'
|
||||||
import { useTeamManager } from '../hooks/useTeamManager'
|
import { useTeamManager } from '../hooks/useTeamManager'
|
||||||
import Button from './Button'
|
import Button from './Button'
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import ThemeProvider from "@/theme/theme-provider";
|
|||||||
import Script from "next/script";
|
import Script from "next/script";
|
||||||
import NotificationCenter from './components/NotificationCenter'
|
import NotificationCenter from './components/NotificationCenter'
|
||||||
import Navbar from "./components/Navbar";
|
import Navbar from "./components/Navbar";
|
||||||
import WebSocketManager from "./components/WebSocketManager";
|
import SSEManager from "./components/SSEManager";
|
||||||
|
|
||||||
const geistSans = Geist({
|
const geistSans = Geist({
|
||||||
variable: "--font-geist-sans",
|
variable: "--font-geist-sans",
|
||||||
@ -40,7 +40,7 @@ export default function RootLayout({
|
|||||||
disableTransitionOnChange
|
disableTransitionOnChange
|
||||||
>
|
>
|
||||||
<Providers>
|
<Providers>
|
||||||
<WebSocketManager />
|
<SSEManager />
|
||||||
{/* Sidebar und Content direkt nebeneinander */}
|
{/* Sidebar und Content direkt nebeneinander */}
|
||||||
<Sidebar>
|
<Sidebar>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
15
src/app/lib/sse-server-client.ts
Normal file
15
src/app/lib/sse-server-client.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
|
||||||
|
const host = 'localhost'
|
||||||
|
|
||||||
|
export async function sendServerSSEMessage(message: any) {
|
||||||
|
try {
|
||||||
|
console.log('[SSE Client] Nachricht senden:', message)
|
||||||
|
await fetch(`http://${host}:3001/send`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(message),
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[SSE Client] Fehler beim Senden:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
72
src/app/lib/useSSEStore.ts
Normal file
72
src/app/lib/useSSEStore.ts
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import { create } from 'zustand'
|
||||||
|
|
||||||
|
type SSEState = {
|
||||||
|
source: EventSource | null
|
||||||
|
isConnected: boolean
|
||||||
|
connect: (steamId: string) => EventSource | undefined
|
||||||
|
disconnect: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useSSE = create<SSEState>((set, get) => {
|
||||||
|
let reconnectTimeout: NodeJS.Timeout | null = null
|
||||||
|
|
||||||
|
const connect = (steamId: string): EventSource | undefined => {
|
||||||
|
const current = get().source
|
||||||
|
if (current) return current // bereits verbunden
|
||||||
|
|
||||||
|
const source = new EventSource(`http://localhost:3001/events?steamId=${steamId}`)
|
||||||
|
|
||||||
|
source.onopen = () => {
|
||||||
|
console.log('[SSE] Verbunden')
|
||||||
|
set({ source, isConnected: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
source.onmessage = (event) => {
|
||||||
|
console.log('[SSE] Nachricht:', event.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
source.addEventListener('notification', (event) => {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse((event as MessageEvent).data)
|
||||||
|
window.dispatchEvent(new CustomEvent(`sse-${data.type}`, { detail: data }))
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[SSE] Ungültige Nachricht:', event)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
source.onerror = (err) => {
|
||||||
|
console.warn('[SSE] Verbindung verloren, versuche Reconnect...')
|
||||||
|
source.close()
|
||||||
|
set({ source: null, isConnected: false })
|
||||||
|
|
||||||
|
if (!reconnectTimeout) {
|
||||||
|
reconnectTimeout = setTimeout(() => {
|
||||||
|
reconnectTimeout = null
|
||||||
|
connect(steamId)
|
||||||
|
}, 3000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set({ source })
|
||||||
|
return source // ✅ wichtig
|
||||||
|
}
|
||||||
|
|
||||||
|
const disconnect = () => {
|
||||||
|
const source = get().source
|
||||||
|
if (source) {
|
||||||
|
source.close()
|
||||||
|
}
|
||||||
|
if (reconnectTimeout) {
|
||||||
|
clearTimeout(reconnectTimeout)
|
||||||
|
reconnectTimeout = null
|
||||||
|
}
|
||||||
|
set({ source: null, isConnected: false })
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
source: null,
|
||||||
|
isConnected: false,
|
||||||
|
connect,
|
||||||
|
disconnect,
|
||||||
|
}
|
||||||
|
})
|
||||||
@ -1,54 +0,0 @@
|
|||||||
export class WebSocketClient {
|
|
||||||
private ws: WebSocket | null = null
|
|
||||||
private baseUrl: string
|
|
||||||
private steamId: string
|
|
||||||
private listeners: ((data: any) => void)[] = []
|
|
||||||
|
|
||||||
constructor(baseUrl: string, steamId: string) {
|
|
||||||
this.baseUrl = baseUrl
|
|
||||||
this.steamId = steamId
|
|
||||||
}
|
|
||||||
|
|
||||||
connect() {
|
|
||||||
if (this.ws && this.ws.readyState === WebSocket.OPEN) return
|
|
||||||
|
|
||||||
const fullUrl = `${this.baseUrl}?steamId=${encodeURIComponent(this.steamId)}`
|
|
||||||
this.ws = new WebSocket(fullUrl)
|
|
||||||
|
|
||||||
this.ws.onopen = () => {
|
|
||||||
console.log('[WebSocket] Verbunden mit Server.')
|
|
||||||
}
|
|
||||||
|
|
||||||
this.ws.onmessage = (event) => {
|
|
||||||
const data = JSON.parse(event.data)
|
|
||||||
//console.log('[WebSocket] Nachricht erhalten:', data)
|
|
||||||
this.listeners.forEach((listener) => listener(data))
|
|
||||||
}
|
|
||||||
|
|
||||||
this.ws.onclose = () => {
|
|
||||||
console.warn('[WebSocket] Verbindung verloren. Reconnect in 3 Sekunden...')
|
|
||||||
setTimeout(() => this.connect(), 3000)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.ws.onerror = (error) => {
|
|
||||||
console.error('[WebSocket] Fehler:', error)
|
|
||||||
this.ws?.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMessage(callback: (data: any) => void) {
|
|
||||||
this.listeners.push(callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
send(message: any) {
|
|
||||||
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
||||||
this.ws.send(JSON.stringify(message))
|
|
||||||
} else {
|
|
||||||
console.warn('[WebSocket] Nachricht konnte nicht gesendet werden.')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
close() {
|
|
||||||
this.ws?.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
const host = 'localhost' // oder deine IP wie '10.0.1.25'
|
|
||||||
|
|
||||||
export async function sendServerWebSocketMessage(message: any) {
|
|
||||||
try {
|
|
||||||
console.log('[WebSocket Client] Message:', message)
|
|
||||||
await fetch(`http://${host}:3001/send`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify(message)
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[WebSocket Client] Fehler beim Senden:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,73 +0,0 @@
|
|||||||
import { create } from 'zustand'
|
|
||||||
|
|
||||||
type WSState = {
|
|
||||||
socket: WebSocket | null
|
|
||||||
connect: (steamId: string) => void
|
|
||||||
disconnect: () => void
|
|
||||||
isConnected: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useWS = create<WSState>((set, get) => {
|
|
||||||
let reconnectTimeout: NodeJS.Timeout | null = null
|
|
||||||
|
|
||||||
const connect = (steamId: string) => {
|
|
||||||
const current = get().socket
|
|
||||||
if (current && (current.readyState === WebSocket.OPEN || current.readyState === WebSocket.CONNECTING)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const ws = new WebSocket(`ws://localhost:3001?steamId=${steamId}`)
|
|
||||||
|
|
||||||
ws.onopen = () => {
|
|
||||||
set({ socket: ws, isConnected: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
ws.onmessage = (event) => {
|
|
||||||
try {
|
|
||||||
const data = JSON.parse(event.data)
|
|
||||||
if (data?.type) {
|
|
||||||
window.dispatchEvent(new CustomEvent(`ws-${data.type}`, { detail: data }))
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error('[WS] Ungültige Nachricht:', event.data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ws.onclose = () => {
|
|
||||||
console.warn('[WS] Verbindung geschlossen. Versuche Reconnect in 3s...')
|
|
||||||
set({ socket: null, isConnected: false })
|
|
||||||
if (!reconnectTimeout) {
|
|
||||||
reconnectTimeout = setTimeout(() => {
|
|
||||||
reconnectTimeout = null
|
|
||||||
connect(steamId)
|
|
||||||
}, 3000)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ws.onerror = (err) => {
|
|
||||||
console.error('[WS] Fehler:', err)
|
|
||||||
ws.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
set({ socket: ws })
|
|
||||||
}
|
|
||||||
|
|
||||||
const disconnect = () => {
|
|
||||||
const socket = get().socket
|
|
||||||
if (socket && socket.readyState === WebSocket.OPEN) {
|
|
||||||
socket.close()
|
|
||||||
}
|
|
||||||
if (reconnectTimeout) {
|
|
||||||
clearTimeout(reconnectTimeout)
|
|
||||||
reconnectTimeout = null
|
|
||||||
}
|
|
||||||
set({ socket: null, isConnected: false })
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
socket: null,
|
|
||||||
isConnected: false,
|
|
||||||
connect,
|
|
||||||
disconnect,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import cron from 'node-cron';
|
import cron from 'node-cron';
|
||||||
import { prisma } from '../app/lib/prisma.js';
|
import { prisma } from '../app/lib/prisma.js';
|
||||||
import { runDownloaderForUser } from './runDownloaderForUser.js';
|
import { runDownloaderForUser } from './runDownloaderForUser.js';
|
||||||
import { sendServerWebSocketMessage } from '../app/lib/websocket-server-client.js';
|
import { sendServerWebSocketMessage } from '../app/lib/sse-server-client.js';
|
||||||
import { decrypt } from '../app/lib/crypto.js';
|
import { decrypt } from '../app/lib/crypto.js';
|
||||||
import { encodeMatch, decodeMatchShareCode } from 'csgo-sharecode';
|
import { encodeMatch, decodeMatchShareCode } from 'csgo-sharecode';
|
||||||
import { log } from '../../scripts/cs2-cron-runner.js';
|
import { log } from '../../scripts/cs2-cron-runner.js';
|
||||||
|
|||||||
87
sse-server.js
Normal file
87
sse-server.js
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
const http = require('http')
|
||||||
|
const url = require('url')
|
||||||
|
|
||||||
|
const clients = new Map()
|
||||||
|
|
||||||
|
// HTTP-Server starten
|
||||||
|
const server = http.createServer((req, res) => {
|
||||||
|
const parsedUrl = url.parse(req.url, true)
|
||||||
|
|
||||||
|
// CORS & SSE-Header
|
||||||
|
res.setHeader('Access-Control-Allow-Origin', '*')
|
||||||
|
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
|
||||||
|
res.setHeader('Access-Control-Allow-Headers', 'Content-Type')
|
||||||
|
|
||||||
|
if (req.method === 'OPTIONS') {
|
||||||
|
res.writeHead(204)
|
||||||
|
res.end()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verbindung zu einem Client (SSE)
|
||||||
|
if (req.method === 'GET' && req.url.startsWith('/events')) {
|
||||||
|
const steamId = parsedUrl.query.steamId
|
||||||
|
if (!steamId) {
|
||||||
|
res.writeHead(400)
|
||||||
|
res.end('steamId fehlt')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// SSE-Header
|
||||||
|
res.writeHead(200, {
|
||||||
|
'Content-Type': 'text/event-stream',
|
||||||
|
'Cache-Control': 'no-cache',
|
||||||
|
'Connection': 'keep-alive',
|
||||||
|
})
|
||||||
|
|
||||||
|
res.write('\n') // Verbindung offen halten
|
||||||
|
clients.set(steamId, res)
|
||||||
|
console.log(`[SSE] Verbunden: steamId=${steamId}`)
|
||||||
|
|
||||||
|
req.on('close', () => {
|
||||||
|
clients.delete(steamId)
|
||||||
|
console.log(`[SSE] Verbindung geschlossen: steamId=${steamId}`)
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nachricht senden (POST)
|
||||||
|
if (req.method === 'POST' && req.url === '/send') {
|
||||||
|
let body = ''
|
||||||
|
req.on('data', chunk => body += chunk)
|
||||||
|
req.on('end', () => {
|
||||||
|
const message = JSON.parse(body)
|
||||||
|
const isBroadcast = !Array.isArray(message.targetUserIds)
|
||||||
|
const type = message.type || 'notification'
|
||||||
|
|
||||||
|
let sentCount = 0
|
||||||
|
|
||||||
|
for (const [steamId, clientRes] of clients.entries()) {
|
||||||
|
const shouldSend =
|
||||||
|
isBroadcast || (
|
||||||
|
Array.isArray(message.targetUserIds) &&
|
||||||
|
message.targetUserIds.includes(steamId)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (shouldSend) {
|
||||||
|
clientRes.write(`event: ${type}\n`)
|
||||||
|
clientRes.write(`data: ${JSON.stringify(message)}\n\n`)
|
||||||
|
sentCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[SSE] Nachricht vom Typ "${type}" an ${sentCount} Client(s) gesendet.`)
|
||||||
|
res.writeHead(200)
|
||||||
|
res.end('Nachricht gesendet.')
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unbekannte Route
|
||||||
|
res.writeHead(404)
|
||||||
|
res.end()
|
||||||
|
})
|
||||||
|
|
||||||
|
server.listen(3001, () => {
|
||||||
|
console.log('✅ SSE-Server läuft auf Port 3001')
|
||||||
|
})
|
||||||
@ -34,7 +34,7 @@
|
|||||||
"**/*.ts",
|
"**/*.ts",
|
||||||
"**/*.tsx",
|
"**/*.tsx",
|
||||||
".next/types/**/*.ts",
|
".next/types/**/*.ts",
|
||||||
"websocket-server.js"
|
"sse-server.js"
|
||||||
],
|
],
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules"]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,89 +0,0 @@
|
|||||||
const { WebSocketServer } = require('ws')
|
|
||||||
const http = require('http')
|
|
||||||
const url = require('url')
|
|
||||||
|
|
||||||
// WebSocket-Server starten
|
|
||||||
const wss = new WebSocketServer({ noServer: true })
|
|
||||||
|
|
||||||
// HTTP-Server zum Empfangen von POST-Anfragen zum Versenden von Nachrichten
|
|
||||||
const server = http.createServer((req, res) => {
|
|
||||||
// CORS Header setzen
|
|
||||||
res.setHeader('Access-Control-Allow-Origin', '*')
|
|
||||||
res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS')
|
|
||||||
res.setHeader('Access-Control-Allow-Headers', 'Content-Type')
|
|
||||||
|
|
||||||
// Preflight-Request
|
|
||||||
if (req.method === 'OPTIONS') {
|
|
||||||
res.writeHead(204)
|
|
||||||
res.end()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nachricht per POST empfangen
|
|
||||||
if (req.method === 'POST' && req.url === '/send') {
|
|
||||||
let body = ''
|
|
||||||
req.on('data', chunk => { body += chunk })
|
|
||||||
req.on('end', () => {
|
|
||||||
const message = JSON.parse(body)
|
|
||||||
const isBroadcast = !Array.isArray(message.targetUserIds)
|
|
||||||
const type = message.type || 'notification'
|
|
||||||
|
|
||||||
let sentCount = 0
|
|
||||||
|
|
||||||
wss.clients.forEach((client) => {
|
|
||||||
const shouldSend =
|
|
||||||
client.readyState === 1 &&
|
|
||||||
(isBroadcast || (
|
|
||||||
Array.isArray(message.targetUserIds) &&
|
|
||||||
client.steamId &&
|
|
||||||
message.targetUserIds.includes(client.steamId)
|
|
||||||
))
|
|
||||||
|
|
||||||
if (shouldSend) {
|
|
||||||
client.send(JSON.stringify({
|
|
||||||
type,
|
|
||||||
...message
|
|
||||||
}))
|
|
||||||
sentCount++
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
console.log(`[WS] Nachricht vom Typ "${type}" an ${sentCount} Client(s) gesendet.`)
|
|
||||||
res.writeHead(200)
|
|
||||||
res.end('Nachricht gesendet.')
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
res.writeHead(404)
|
|
||||||
res.end()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
wss.on('connection', (ws, req) => {
|
|
||||||
const parsedUrl = url.parse(req.url, true)
|
|
||||||
const steamId = parsedUrl.query.steamId
|
|
||||||
|
|
||||||
if (!steamId) {
|
|
||||||
console.warn('[WS] Verbindung ohne steamId - wird geschlossen')
|
|
||||||
ws.close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ws.steamId = steamId
|
|
||||||
console.log(`[WS] Verbunden: steamId=${steamId}`)
|
|
||||||
|
|
||||||
ws.on('close', () => {
|
|
||||||
console.log(`[WS] Verbindung geschlossen für ${steamId}`)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// WebSocket Upgrade akzeptieren
|
|
||||||
server.on('upgrade', (req, socket, head) => {
|
|
||||||
wss.handleUpgrade(req, socket, head, (ws) => {
|
|
||||||
wss.emit('connection', ws, req)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// Server starten
|
|
||||||
server.listen(3001, () => {
|
|
||||||
console.log('✅ WebSocket Server läuft auf Port 3001')
|
|
||||||
})
|
|
||||||
Loading…
x
Reference in New Issue
Block a user