Fix: WebSocket-Konflikt zwischen Streaming und Watch Together
Root Cause: ws-Library killt WS-Verbindungen mit HTTP 400 wenn
mehrere WebSocketServer mit { server, path } registriert werden.
Der erste WSS (streaming) hat abortHandshake() fuer watch-together
Verbindungen aufgerufen.
- Beide WSS auf noServer-Modus umgestellt
- Zentrales upgrade-Routing in index.ts nach Pathname
- Frontend: connectWs prueft jetzt auch CONNECTING-State
- Frontend: onclose clobbert wsRef nur noch wenn gleicher Socket
- Frontend: waitForWs hat jetzt 10s Timeout statt Endlosschleife
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
e748fc97e9
commit
e4895a792c
6 changed files with 60 additions and 32 deletions
|
|
@ -1,6 +1,7 @@
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import http from 'node:http';
|
import http from 'node:http';
|
||||||
|
import { WebSocketServer } from 'ws';
|
||||||
import { Client } from 'discord.js';
|
import { Client } from 'discord.js';
|
||||||
import { createClient } from './core/discord.js';
|
import { createClient } from './core/discord.js';
|
||||||
import { addSSEClient, removeSSEClient, sseBroadcast, getSSEClientCount } from './core/sse.js';
|
import { addSSEClient, removeSSEClient, sseBroadcast, getSSEClientCount } from './core/sse.js';
|
||||||
|
|
@ -168,8 +169,29 @@ async function boot(): Promise<void> {
|
||||||
|
|
||||||
// Start Express (http.createServer so WebSocket can attach)
|
// Start Express (http.createServer so WebSocket can attach)
|
||||||
const httpServer = http.createServer(app);
|
const httpServer = http.createServer(app);
|
||||||
attachWebSocket(httpServer);
|
|
||||||
attachWatchTogetherWs(httpServer);
|
// Create WebSocket servers (noServer mode to avoid path conflicts)
|
||||||
|
const wssStreaming = new WebSocketServer({ noServer: true });
|
||||||
|
const wssWatchTogether = new WebSocketServer({ noServer: true });
|
||||||
|
|
||||||
|
// Route WebSocket upgrade requests to the correct server
|
||||||
|
httpServer.on('upgrade', (request, socket, head) => {
|
||||||
|
const { pathname } = new URL(request.url!, `http://${request.headers.host}`);
|
||||||
|
if (pathname === '/ws/streaming') {
|
||||||
|
wssStreaming.handleUpgrade(request, socket, head, (ws) => {
|
||||||
|
wssStreaming.emit('connection', ws, request);
|
||||||
|
});
|
||||||
|
} else if (pathname === '/ws/watch-together') {
|
||||||
|
wssWatchTogether.handleUpgrade(request, socket, head, (ws) => {
|
||||||
|
wssWatchTogether.emit('connection', ws, request);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
socket.destroy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
attachWebSocket(wssStreaming);
|
||||||
|
attachWatchTogetherWs(wssWatchTogether);
|
||||||
httpServer.listen(PORT, () => console.log(`[HTTP] Listening on :${PORT}`));
|
httpServer.listen(PORT, () => console.log(`[HTTP] Listening on :${PORT}`));
|
||||||
|
|
||||||
// Login Discord bots
|
// Login Discord bots
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import type express from 'express';
|
import type express from 'express';
|
||||||
import http from 'node:http';
|
|
||||||
import { WebSocketServer, WebSocket } from 'ws';
|
import { WebSocketServer, WebSocket } from 'ws';
|
||||||
import crypto from 'node:crypto';
|
import crypto from 'node:crypto';
|
||||||
import type { Plugin, PluginContext } from '../../core/plugin.js';
|
import type { Plugin, PluginContext } from '../../core/plugin.js';
|
||||||
|
|
@ -265,9 +264,9 @@ const streamingPlugin: Plugin = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Call after httpServer is created to attach WebSocket signaling */
|
/** Attach WebSocket signaling to a pre-created WebSocketServer (noServer mode) */
|
||||||
export function attachWebSocket(server: http.Server): void {
|
export function attachWebSocket(existingWss: WebSocketServer): void {
|
||||||
wss = new WebSocketServer({ server, path: '/ws/streaming' });
|
wss = existingWss;
|
||||||
|
|
||||||
wss.on('connection', (ws) => {
|
wss.on('connection', (ws) => {
|
||||||
const clientId = crypto.randomUUID();
|
const clientId = crypto.randomUUID();
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import type express from 'express';
|
import type express from 'express';
|
||||||
import http from 'node:http';
|
|
||||||
import { WebSocketServer, WebSocket } from 'ws';
|
import { WebSocketServer, WebSocket } from 'ws';
|
||||||
import crypto from 'node:crypto';
|
import crypto from 'node:crypto';
|
||||||
import type { Plugin, PluginContext } from '../../core/plugin.js';
|
import type { Plugin, PluginContext } from '../../core/plugin.js';
|
||||||
|
|
@ -515,9 +514,9 @@ const watchTogetherPlugin: Plugin = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Call after httpServer is created to attach WebSocket */
|
/** Attach WebSocket to a pre-created WebSocketServer (noServer mode) */
|
||||||
export function attachWatchTogetherWs(server: http.Server): void {
|
export function attachWatchTogetherWs(existingWss: WebSocketServer): void {
|
||||||
wss = new WebSocketServer({ server, path: '/ws/watch-together' });
|
wss = existingWss;
|
||||||
|
|
||||||
wss.on('connection', (ws) => {
|
wss.on('connection', (ws) => {
|
||||||
const clientId = crypto.randomUUID();
|
const clientId = crypto.randomUUID();
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
2
web/dist/index.html
vendored
2
web/dist/index.html
vendored
|
|
@ -5,7 +5,7 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Gaming Hub</title>
|
<title>Gaming Hub</title>
|
||||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🎮</text></svg>" />
|
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🎮</text></svg>" />
|
||||||
<script type="module" crossorigin src="/assets/index-CgrjD6IO.js"></script>
|
<script type="module" crossorigin src="/assets/index-DKV7w-rf.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-C2eno-Si.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-C2eno-Si.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
|
||||||
|
|
@ -367,7 +367,7 @@ export default function WatchTogetherTab({ data }: { data: any }) {
|
||||||
|
|
||||||
// ── WebSocket connect ──
|
// ── WebSocket connect ──
|
||||||
const connectWs = useCallback(() => {
|
const connectWs = useCallback(() => {
|
||||||
if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) return;
|
if (wsRef.current && (wsRef.current.readyState === WebSocket.OPEN || wsRef.current.readyState === WebSocket.CONNECTING)) return;
|
||||||
|
|
||||||
const proto = location.protocol === 'https:' ? 'wss' : 'ws';
|
const proto = location.protocol === 'https:' ? 'wss' : 'ws';
|
||||||
const ws = new WebSocket(`${proto}://${location.host}/ws/watch-together`);
|
const ws = new WebSocket(`${proto}://${location.host}/ws/watch-together`);
|
||||||
|
|
@ -382,7 +382,9 @@ export default function WatchTogetherTab({ data }: { data: any }) {
|
||||||
};
|
};
|
||||||
|
|
||||||
ws.onclose = () => {
|
ws.onclose = () => {
|
||||||
wsRef.current = null;
|
if (wsRef.current === ws) {
|
||||||
|
wsRef.current = null;
|
||||||
|
}
|
||||||
if (currentRoomRef.current) {
|
if (currentRoomRef.current) {
|
||||||
reconnectTimerRef.current = setTimeout(() => {
|
reconnectTimerRef.current = setTimeout(() => {
|
||||||
reconnectDelayRef.current = Math.min(reconnectDelayRef.current * 2, 10000);
|
reconnectDelayRef.current = Math.min(reconnectDelayRef.current * 2, 10000);
|
||||||
|
|
@ -401,6 +403,7 @@ export default function WatchTogetherTab({ data }: { data: any }) {
|
||||||
setError(null);
|
setError(null);
|
||||||
connectWs();
|
connectWs();
|
||||||
|
|
||||||
|
const startTime = Date.now();
|
||||||
const waitForWs = () => {
|
const waitForWs = () => {
|
||||||
if (wsRef.current?.readyState === WebSocket.OPEN) {
|
if (wsRef.current?.readyState === WebSocket.OPEN) {
|
||||||
wsSend({
|
wsSend({
|
||||||
|
|
@ -409,6 +412,8 @@ export default function WatchTogetherTab({ data }: { data: any }) {
|
||||||
userName: userName.trim(),
|
userName: userName.trim(),
|
||||||
password: roomPassword.trim() || undefined,
|
password: roomPassword.trim() || undefined,
|
||||||
});
|
});
|
||||||
|
} else if (Date.now() - startTime > 10000) {
|
||||||
|
setError('Verbindung zum Server fehlgeschlagen.');
|
||||||
} else {
|
} else {
|
||||||
setTimeout(waitForWs, 100);
|
setTimeout(waitForWs, 100);
|
||||||
}
|
}
|
||||||
|
|
@ -422,6 +427,7 @@ export default function WatchTogetherTab({ data }: { data: any }) {
|
||||||
setError(null);
|
setError(null);
|
||||||
connectWs();
|
connectWs();
|
||||||
|
|
||||||
|
const startTime = Date.now();
|
||||||
const waitForWs = () => {
|
const waitForWs = () => {
|
||||||
if (wsRef.current?.readyState === WebSocket.OPEN) {
|
if (wsRef.current?.readyState === WebSocket.OPEN) {
|
||||||
wsSend({
|
wsSend({
|
||||||
|
|
@ -430,6 +436,8 @@ export default function WatchTogetherTab({ data }: { data: any }) {
|
||||||
roomId,
|
roomId,
|
||||||
password: password?.trim() || undefined,
|
password: password?.trim() || undefined,
|
||||||
});
|
});
|
||||||
|
} else if (Date.now() - startTime > 10000) {
|
||||||
|
setError('Verbindung zum Server fehlgeschlagen.');
|
||||||
} else {
|
} else {
|
||||||
setTimeout(waitForWs, 100);
|
setTimeout(waitForWs, 100);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue