debugging eingebaut, deploy gefixt
All checks were successful
Deploy SingleChat / deploy (push) Successful in 25s
All checks were successful
Deploy SingleChat / deploy (push) Successful in 25s
This commit is contained in:
14
.env.example
Normal file
14
.env.example
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
NODE_ENV=production
|
||||||
|
PORT=4000
|
||||||
|
SESSION_SECRET=
|
||||||
|
|
||||||
|
# Relay-only Videochat via TURN
|
||||||
|
VIDEO_TURN_URLS=
|
||||||
|
VIDEO_TURN_USERNAME=
|
||||||
|
VIDEO_TURN_CREDENTIAL=
|
||||||
|
|
||||||
|
# Optional zusaetzliche STUN-Server
|
||||||
|
VIDEO_STUN_URLS=
|
||||||
|
|
||||||
|
# Optional: komplette ICE-Serverliste als JSON statt der Einzelvariablen
|
||||||
|
# VIDEO_ICE_SERVERS_JSON=
|
||||||
@@ -183,6 +183,25 @@ Die folgenden Umgebungsvariablen können in `.env` gesetzt werden:
|
|||||||
- `NODE_ENV`: `production` (automatisch gesetzt)
|
- `NODE_ENV`: `production` (automatisch gesetzt)
|
||||||
- `PORT`: `4000` (Standard)
|
- `PORT`: `4000` (Standard)
|
||||||
- `SESSION_SECRET`: Zufälliges Secret für Sessions (wird von install.sh generiert)
|
- `SESSION_SECRET`: Zufälliges Secret für Sessions (wird von install.sh generiert)
|
||||||
|
- `VIDEO_TURN_URLS`: Kommagetrennte `turn:`/`turns:`-URLs für den Relay-Medienserver
|
||||||
|
- `VIDEO_TURN_USERNAME`: TURN-Benutzername
|
||||||
|
- `VIDEO_TURN_CREDENTIAL`: TURN-Passwort
|
||||||
|
- `VIDEO_STUN_URLS`: Optional kommagetrennte `stun:`-URLs
|
||||||
|
- `VIDEO_ICE_SERVERS_JSON`: Optional komplette ICE-Serverliste als JSON statt der Einzelvariablen
|
||||||
|
|
||||||
|
Beispiel:
|
||||||
|
|
||||||
|
```env
|
||||||
|
NODE_ENV=production
|
||||||
|
PORT=4000
|
||||||
|
SESSION_SECRET=bitte-eigenes-starkes-secret-setzen
|
||||||
|
VIDEO_TURN_URLS=turn:turn.ypchat.net:3478?transport=udp,turn:turn.ypchat.net:3478?transport=tcp
|
||||||
|
VIDEO_TURN_USERNAME=ypchat
|
||||||
|
VIDEO_TURN_CREDENTIAL=dein-turn-passwort
|
||||||
|
VIDEO_STUN_URLS=stun:turn.ypchat.net:3478
|
||||||
|
```
|
||||||
|
|
||||||
|
Die Deploy-Skripte synchronisieren `.env` jetzt mit `.env.example`, behalten dabei aber vorhandene Werte aus der bisherigen `.env` bei, statt sie zu überschreiben.
|
||||||
|
|
||||||
## Sicherheit
|
## Sicherheit
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ const MAX_VIDEO_CONNECTIONS_DEFAULT = 3;
|
|||||||
const VIDEO_TERMINAL_STATUSES = new Set(['rejected', 'cancelled', 'ended', 'failed']);
|
const VIDEO_TERMINAL_STATUSES = new Set(['rejected', 'cancelled', 'ended', 'failed']);
|
||||||
const VIDEO_LIVE_STATUSES = new Set(['ringing', 'connecting', 'active']);
|
const VIDEO_LIVE_STATUSES = new Set(['ringing', 'connecting', 'active']);
|
||||||
const WEBRTC_CONNECTION_STATES = new Set(['new', 'connecting', 'connected', 'disconnected', 'failed', 'closed']);
|
const WEBRTC_CONNECTION_STATES = new Set(['new', 'connecting', 'connected', 'disconnected', 'failed', 'closed']);
|
||||||
|
const VIDEO_CONNECT_TIMEOUT_MS = 20000;
|
||||||
const VIDEO_STATUS_ORDER = {
|
const VIDEO_STATUS_ORDER = {
|
||||||
ringing: 1,
|
ringing: 1,
|
||||||
connecting: 2,
|
connecting: 2,
|
||||||
@@ -221,6 +222,9 @@ export const useChatStore = defineStore('chat', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (runtime.connectTimeoutId) {
|
||||||
|
window.clearTimeout(runtime.connectTimeoutId);
|
||||||
|
}
|
||||||
runtime.pc.ontrack = null;
|
runtime.pc.ontrack = null;
|
||||||
runtime.pc.onicecandidate = null;
|
runtime.pc.onicecandidate = null;
|
||||||
runtime.pc.onconnectionstatechange = null;
|
runtime.pc.onconnectionstatechange = null;
|
||||||
@@ -361,7 +365,8 @@ export const useChatStore = defineStore('chat', () => {
|
|||||||
pc,
|
pc,
|
||||||
localStream,
|
localStream,
|
||||||
pendingCandidates: [],
|
pendingCandidates: [],
|
||||||
offerCreated: false
|
offerCreated: false,
|
||||||
|
connectTimeoutId: null
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const track of localStream.getTracks()) {
|
for (const track of localStream.getTracks()) {
|
||||||
@@ -391,6 +396,10 @@ export const useChatStore = defineStore('chat', () => {
|
|||||||
|
|
||||||
pc.onconnectionstatechange = () => {
|
pc.onconnectionstatechange = () => {
|
||||||
const state = pc.connectionState;
|
const state = pc.connectionState;
|
||||||
|
if (state === 'connected' && runtime.connectTimeoutId) {
|
||||||
|
window.clearTimeout(runtime.connectTimeoutId);
|
||||||
|
runtime.connectTimeoutId = null;
|
||||||
|
}
|
||||||
if (WEBRTC_CONNECTION_STATES.has(state)) {
|
if (WEBRTC_CONNECTION_STATES.has(state)) {
|
||||||
emitConnectionState(session.callId, state);
|
emitConnectionState(session.callId, state);
|
||||||
}
|
}
|
||||||
@@ -442,7 +451,17 @@ export const useChatStore = defineStore('chat', () => {
|
|||||||
async function startVideoMediaForSession(session) {
|
async function startVideoMediaForSession(session) {
|
||||||
if (!session?.media) return;
|
if (!session?.media) return;
|
||||||
try {
|
try {
|
||||||
await ensurePeerConnectionForSession(session);
|
const runtime = await ensurePeerConnectionForSession(session);
|
||||||
|
if (runtime && !runtime.connectTimeoutId) {
|
||||||
|
runtime.connectTimeoutId = window.setTimeout(() => {
|
||||||
|
const activeRuntime = peerConnections.get(session.callId);
|
||||||
|
const state = activeRuntime?.pc?.connectionState || 'new';
|
||||||
|
if (state !== 'connected') {
|
||||||
|
setTemporaryError('Videoverbindung konnte nicht aufgebaut werden. TURN-Server, Ports und Firewall prüfen.', 7000);
|
||||||
|
emitConnectionState(session.callId, 'failed');
|
||||||
|
}
|
||||||
|
}, VIDEO_CONNECT_TIMEOUT_MS);
|
||||||
|
}
|
||||||
await maybeCreateOffer(session);
|
await maybeCreateOffer(session);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Video-Medienpfad konnte nicht gestartet werden:', error);
|
console.error('Video-Medienpfad konnte nicht gestartet werden:', error);
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|||||||
TARGET_DIR="/opt/ypchat"
|
TARGET_DIR="/opt/ypchat"
|
||||||
USER="www-data"
|
USER="www-data"
|
||||||
GROUP="www-data"
|
GROUP="www-data"
|
||||||
|
ENV_TEMPLATE="$SOURCE_DIR/.env.example"
|
||||||
|
ENV_MERGE_SCRIPT="$SOURCE_DIR/scripts/merge-env-template.sh"
|
||||||
|
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
echo "YpChat Deployment nach /opt/ypchat"
|
echo "YpChat Deployment nach /opt/ypchat"
|
||||||
@@ -54,6 +56,15 @@ rsync -av --progress \
|
|||||||
|
|
||||||
echo "✓ Dateien kopiert"
|
echo "✓ Dateien kopiert"
|
||||||
|
|
||||||
|
if [ ! -f "$ENV_TEMPLATE" ]; then
|
||||||
|
echo "FEHLER: Env-Vorlage fehlt: $ENV_TEMPLATE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -x "$ENV_MERGE_SCRIPT" ]; then
|
||||||
|
chmod +x "$ENV_MERGE_SCRIPT"
|
||||||
|
fi
|
||||||
|
|
||||||
# Setze Besitzer
|
# Setze Besitzer
|
||||||
echo "Setze Besitzer auf $USER:$GROUP..."
|
echo "Setze Besitzer auf $USER:$GROUP..."
|
||||||
chown -R $USER:$GROUP "$TARGET_DIR"
|
chown -R $USER:$GROUP "$TARGET_DIR"
|
||||||
@@ -111,22 +122,12 @@ chown -R $USER:$GROUP "$TARGET_DIR/docroot/dist"
|
|||||||
|
|
||||||
echo "✓ Dateien kopiert"
|
echo "✓ Dateien kopiert"
|
||||||
|
|
||||||
# Erstelle .env Datei falls nicht vorhanden
|
|
||||||
if [ ! -f "$TARGET_DIR/.env" ]; then
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "Erstelle .env Datei..."
|
echo "Synchronisiere .env Datei mit Vorlage..."
|
||||||
SESSION_SECRET=$(openssl rand -hex 32)
|
SESSION_SECRET="$(openssl rand -hex 32)"
|
||||||
cat > "$TARGET_DIR/.env" << EOF
|
"$ENV_MERGE_SCRIPT" "$TARGET_DIR/.env.example" "$TARGET_DIR/.env" "$SESSION_SECRET"
|
||||||
NODE_ENV=production
|
|
||||||
PORT=4000
|
|
||||||
SESSION_SECRET=$SESSION_SECRET
|
|
||||||
EOF
|
|
||||||
chown $USER:$GROUP "$TARGET_DIR/.env"
|
chown $USER:$GROUP "$TARGET_DIR/.env"
|
||||||
echo "✓ .env Datei erstellt"
|
echo "✓ .env Datei synchronisiert (bestehende Werte beibehalten)"
|
||||||
echo "SESSION_SECRET wurde generiert: $SESSION_SECRET"
|
|
||||||
else
|
|
||||||
echo "✓ .env Datei existiert bereits"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ DEPLOY_GROUP="${DEPLOY_GROUP:-$(id -gn "$DEPLOY_USER")}"
|
|||||||
LOCK_DIR="${LOCK_DIR:-/tmp/actualize-singlechat}"
|
LOCK_DIR="${LOCK_DIR:-/tmp/actualize-singlechat}"
|
||||||
LOCK_FILE="${LOCK_FILE:-$LOCK_DIR/deploy.lock}"
|
LOCK_FILE="${LOCK_FILE:-$LOCK_DIR/deploy.lock}"
|
||||||
NPM_CACHE_DIR="${NPM_CACHE_DIR:-$APP_DIR/.npm-cache}"
|
NPM_CACHE_DIR="${NPM_CACHE_DIR:-$APP_DIR/.npm-cache}"
|
||||||
|
ENV_TEMPLATE="${ENV_TEMPLATE:-$APP_DIR/.env.example}"
|
||||||
|
ENV_MERGE_SCRIPT="${ENV_MERGE_SCRIPT:-$APP_DIR/scripts/merge-env-template.sh}"
|
||||||
|
|
||||||
log() {
|
log() {
|
||||||
printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*"
|
printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*"
|
||||||
@@ -128,16 +130,19 @@ run_as_deploy_user npm ci
|
|||||||
log "Installiere Client-Dependencies"
|
log "Installiere Client-Dependencies"
|
||||||
run_as_deploy_user npm --prefix client ci
|
run_as_deploy_user npm --prefix client ci
|
||||||
|
|
||||||
if [ ! -f "$APP_DIR/.env" ]; then
|
if [ ! -f "$ENV_TEMPLATE" ]; then
|
||||||
log "Erstelle .env"
|
echo "FEHLER: Env-Vorlage fehlt: $ENV_TEMPLATE" >&2
|
||||||
session_secret="$(openssl rand -hex 32)"
|
exit 1
|
||||||
cat > "$APP_DIR/.env" <<EOF
|
|
||||||
NODE_ENV=production
|
|
||||||
PORT=4000
|
|
||||||
SESSION_SECRET=$session_secret
|
|
||||||
EOF
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ ! -x "$ENV_MERGE_SCRIPT" ]; then
|
||||||
|
chmod +x "$ENV_MERGE_SCRIPT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Synchronisiere .env mit Vorlage"
|
||||||
|
session_secret="$(openssl rand -hex 32)"
|
||||||
|
"$ENV_MERGE_SCRIPT" "$ENV_TEMPLATE" "$APP_DIR/.env" "$session_secret"
|
||||||
|
|
||||||
if [ "$(id -u)" -eq 0 ]; then
|
if [ "$(id -u)" -eq 0 ]; then
|
||||||
chown "$RUN_USER:$RUN_GROUP" "$APP_DIR/.env"
|
chown "$RUN_USER:$RUN_GROUP" "$APP_DIR/.env"
|
||||||
fi
|
fi
|
||||||
|
|||||||
77
scripts/merge-env-template.sh
Normal file
77
scripts/merge-env-template.sh
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [ "$#" -lt 2 ] || [ "$#" -gt 3 ]; then
|
||||||
|
echo "Usage: $0 <template-file> <env-file> [session-secret]" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
template_file="$1"
|
||||||
|
env_file="$2"
|
||||||
|
session_secret="${3:-}"
|
||||||
|
|
||||||
|
if [ ! -f "$template_file" ]; then
|
||||||
|
echo "Template file not found: $template_file" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
tmp_output="$(mktemp)"
|
||||||
|
trap 'rm -f "$tmp_output"' EXIT
|
||||||
|
|
||||||
|
if [ -f "$env_file" ]; then
|
||||||
|
awk '
|
||||||
|
function key_from(line, key) {
|
||||||
|
if (line ~ /^[[:space:]]*[A-Za-z_][A-Za-z0-9_]*=/) {
|
||||||
|
key = line
|
||||||
|
sub(/=.*/, "", key)
|
||||||
|
gsub(/^[[:space:]]+|[[:space:]]+$/, "", key)
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
FNR == NR {
|
||||||
|
key = key_from($0)
|
||||||
|
if (key != "") {
|
||||||
|
existing[key] = $0
|
||||||
|
existing_order[++existing_count] = key
|
||||||
|
}
|
||||||
|
next
|
||||||
|
}
|
||||||
|
{
|
||||||
|
key = key_from($0)
|
||||||
|
if (key != "") {
|
||||||
|
template_seen[key] = 1
|
||||||
|
if (key in existing) {
|
||||||
|
print existing[key]
|
||||||
|
} else {
|
||||||
|
print $0
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print $0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
END {
|
||||||
|
appended = 0
|
||||||
|
for (i = 1; i <= existing_count; i++) {
|
||||||
|
key = existing_order[i]
|
||||||
|
if (!(key in template_seen)) {
|
||||||
|
if (!appended) {
|
||||||
|
print ""
|
||||||
|
print "# Vorherige zusaetzliche Eintraege"
|
||||||
|
appended = 1
|
||||||
|
}
|
||||||
|
print existing[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
' "$env_file" "$template_file" > "$tmp_output"
|
||||||
|
else
|
||||||
|
cp "$template_file" "$tmp_output"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$session_secret" ] && grep -q '^SESSION_SECRET=$' "$tmp_output"; then
|
||||||
|
sed -i "s/^SESSION_SECRET=$/SESSION_SECRET=$session_secret/" "$tmp_output"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mv "$tmp_output" "$env_file"
|
||||||
@@ -418,9 +418,19 @@ function buildVideoCallPayloadForUser(session, userName) {
|
|||||||
function emitVideoCallEventToUser(userName, eventName, session) {
|
function emitVideoCallEventToUser(userName, eventName, session) {
|
||||||
const client = getClientByUserName(userName);
|
const client = getClientByUserName(userName);
|
||||||
if (!isClientOnline(client)) return;
|
if (!isClientOnline(client)) return;
|
||||||
|
console.log('[video] emit', eventName, {
|
||||||
|
to: userName,
|
||||||
|
callId: session.callId,
|
||||||
|
withUserName: getOtherParticipant(session, userName),
|
||||||
|
status: session.status
|
||||||
|
});
|
||||||
client.socket.emit(eventName, buildVideoCallPayloadForUser(session, userName));
|
client.socket.emit(eventName, buildVideoCallPayloadForUser(session, userName));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function logVideo(message, details = {}) {
|
||||||
|
console.log('[video]', message, details);
|
||||||
|
}
|
||||||
|
|
||||||
function emitVideoCapacityToUser(userName) {
|
function emitVideoCapacityToUser(userName) {
|
||||||
const client = getClientByUserName(userName);
|
const client = getClientByUserName(userName);
|
||||||
if (!isClientOnline(client)) return;
|
if (!isClientOnline(client)) return;
|
||||||
@@ -503,6 +513,7 @@ function finalizeVideoSession(session) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function sendVideoCallError(socket, code, message, details = {}) {
|
function sendVideoCallError(socket, code, message, details = {}) {
|
||||||
|
logVideo('error', { code, message, ...details });
|
||||||
socket.emit('videoCall:error', {
|
socket.emit('videoCall:error', {
|
||||||
code,
|
code,
|
||||||
message,
|
message,
|
||||||
@@ -1874,6 +1885,7 @@ export function setupBroadcast(io, __dirname) {
|
|||||||
|
|
||||||
function handleVideoCallInvite(socket, client, data) {
|
function handleVideoCallInvite(socket, client, data) {
|
||||||
const withUserName = String(data?.withUserName || '').trim();
|
const withUserName = String(data?.withUserName || '').trim();
|
||||||
|
logVideo('invite:request', { from: client.userName, withUserName });
|
||||||
if (!withUserName || withUserName === client.userName) {
|
if (!withUserName || withUserName === client.userName) {
|
||||||
sendVideoCallError(socket, 'VIDEO_INVALID_PARTNER', 'Ungültiger Gesprächspartner.');
|
sendVideoCallError(socket, 'VIDEO_INVALID_PARTNER', 'Ungültiger Gesprächspartner.');
|
||||||
return;
|
return;
|
||||||
@@ -1916,6 +1928,7 @@ export function setupBroadcast(io, __dirname) {
|
|||||||
|
|
||||||
const session = createVideoSession(client.userName, withUserName);
|
const session = createVideoSession(client.userName, withUserName);
|
||||||
registerVideoSession(session);
|
registerVideoSession(session);
|
||||||
|
logVideo('invite:created', { callId: session.callId, from: client.userName, withUserName, status: session.status });
|
||||||
|
|
||||||
emitVideoCallEventToUser(client.userName, 'videoCall:invite', session);
|
emitVideoCallEventToUser(client.userName, 'videoCall:invite', session);
|
||||||
emitVideoCallEventToUser(withUserName, 'videoCall:incoming', session);
|
emitVideoCallEventToUser(withUserName, 'videoCall:incoming', session);
|
||||||
@@ -1926,6 +1939,7 @@ export function setupBroadcast(io, __dirname) {
|
|||||||
function handleVideoCallAccept(socket, client, data) {
|
function handleVideoCallAccept(socket, client, data) {
|
||||||
const callId = String(data?.callId || '').trim();
|
const callId = String(data?.callId || '').trim();
|
||||||
const session = videoSessions.get(callId);
|
const session = videoSessions.get(callId);
|
||||||
|
logVideo('accept:request', { from: client.userName, callId });
|
||||||
|
|
||||||
if (!session || session.status !== 'ringing' || !session.participants.includes(client.userName)) {
|
if (!session || session.status !== 'ringing' || !session.participants.includes(client.userName)) {
|
||||||
sendVideoCallError(socket, 'VIDEO_CALL_NOT_FOUND', 'Videoanruf nicht gefunden.', { callId });
|
sendVideoCallError(socket, 'VIDEO_CALL_NOT_FOUND', 'Videoanruf nicht gefunden.', { callId });
|
||||||
@@ -1955,6 +1969,7 @@ export function setupBroadcast(io, __dirname) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
touchVideoSession(session, 'connecting');
|
touchVideoSession(session, 'connecting');
|
||||||
|
logVideo('accept:connecting', { callId: session.callId, participants: session.participants });
|
||||||
emitVideoCallEventToUser(session.participants[0], 'videoCall:start', session);
|
emitVideoCallEventToUser(session.participants[0], 'videoCall:start', session);
|
||||||
emitVideoCallEventToUser(session.participants[1], 'videoCall:start', session);
|
emitVideoCallEventToUser(session.participants[1], 'videoCall:start', session);
|
||||||
emitVideoCapacityToUser(session.participants[0]);
|
emitVideoCapacityToUser(session.participants[0]);
|
||||||
@@ -2044,6 +2059,7 @@ export function setupBroadcast(io, __dirname) {
|
|||||||
const callId = String(data?.callId || '').trim();
|
const callId = String(data?.callId || '').trim();
|
||||||
const signalType = String(data?.signalType || '').trim();
|
const signalType = String(data?.signalType || '').trim();
|
||||||
const session = videoSessions.get(callId);
|
const session = videoSessions.get(callId);
|
||||||
|
logVideo('signal:received', { from: client.userName, callId, signalType });
|
||||||
|
|
||||||
if (!session || !ACTIVE_VIDEO_SESSION_STATUSES.has(session.status) || !session.participants.includes(client.userName)) {
|
if (!session || !ACTIVE_VIDEO_SESSION_STATUSES.has(session.status) || !session.participants.includes(client.userName)) {
|
||||||
sendVideoCallError(socket, 'VIDEO_CALL_NOT_FOUND', 'Videoanruf nicht gefunden.', { callId });
|
sendVideoCallError(socket, 'VIDEO_CALL_NOT_FOUND', 'Videoanruf nicht gefunden.', { callId });
|
||||||
@@ -2061,6 +2077,7 @@ export function setupBroadcast(io, __dirname) {
|
|||||||
const description = data?.description;
|
const description = data?.description;
|
||||||
const descriptionType = String(description?.type || '').trim();
|
const descriptionType = String(description?.type || '').trim();
|
||||||
const sdp = String(description?.sdp || '').trim();
|
const sdp = String(description?.sdp || '').trim();
|
||||||
|
logVideo('signal:description', { from: client.userName, to: otherUserName, callId, descriptionType, sdpLength: sdp.length });
|
||||||
if (!descriptionType || !sdp || !['offer', 'answer'].includes(descriptionType)) {
|
if (!descriptionType || !sdp || !['offer', 'answer'].includes(descriptionType)) {
|
||||||
sendVideoCallError(socket, 'VIDEO_SIGNAL_INVALID_DESCRIPTION', 'Ungültige Video-Signalisierung.', { callId });
|
sendVideoCallError(socket, 'VIDEO_SIGNAL_INVALID_DESCRIPTION', 'Ungültige Video-Signalisierung.', { callId });
|
||||||
return;
|
return;
|
||||||
@@ -2077,6 +2094,13 @@ export function setupBroadcast(io, __dirname) {
|
|||||||
|
|
||||||
if (signalType === 'candidate') {
|
if (signalType === 'candidate') {
|
||||||
const candidate = data?.candidate;
|
const candidate = data?.candidate;
|
||||||
|
logVideo('signal:candidate', {
|
||||||
|
from: client.userName,
|
||||||
|
to: otherUserName,
|
||||||
|
callId,
|
||||||
|
candidateType: candidate?.type || null,
|
||||||
|
candidateSnippet: String(candidate?.candidate || '').slice(0, 120)
|
||||||
|
});
|
||||||
if (!isRelayIceCandidate(candidate)) {
|
if (!isRelayIceCandidate(candidate)) {
|
||||||
sendVideoCallError(socket, 'VIDEO_SIGNAL_NON_RELAY_CANDIDATE', 'Nur Relay-Kandidaten sind für Videochat erlaubt.', { callId });
|
sendVideoCallError(socket, 'VIDEO_SIGNAL_NON_RELAY_CANDIDATE', 'Nur Relay-Kandidaten sind für Videochat erlaubt.', { callId });
|
||||||
return;
|
return;
|
||||||
@@ -2092,6 +2116,7 @@ export function setupBroadcast(io, __dirname) {
|
|||||||
const callId = String(data?.callId || '').trim();
|
const callId = String(data?.callId || '').trim();
|
||||||
const nextState = String(data?.connectionState || '').trim().toLowerCase();
|
const nextState = String(data?.connectionState || '').trim().toLowerCase();
|
||||||
const session = videoSessions.get(callId);
|
const session = videoSessions.get(callId);
|
||||||
|
logVideo('connection-state', { from: client.userName, callId, nextState });
|
||||||
|
|
||||||
if (!session || !ACTIVE_VIDEO_SESSION_STATUSES.has(session.status) || !session.participants.includes(client.userName)) {
|
if (!session || !ACTIVE_VIDEO_SESSION_STATUSES.has(session.status) || !session.participants.includes(client.userName)) {
|
||||||
sendVideoCallError(socket, 'VIDEO_CALL_NOT_FOUND', 'Videoanruf nicht gefunden.', { callId });
|
sendVideoCallError(socket, 'VIDEO_CALL_NOT_FOUND', 'Videoanruf nicht gefunden.', { callId });
|
||||||
@@ -2117,6 +2142,7 @@ export function setupBroadcast(io, __dirname) {
|
|||||||
session.participants.every((participant) => session.connectionStates[participant] === 'connected')
|
session.participants.every((participant) => session.connectionStates[participant] === 'connected')
|
||||||
) {
|
) {
|
||||||
touchVideoSession(session, 'active');
|
touchVideoSession(session, 'active');
|
||||||
|
logVideo('connection-state:active', { callId: session.callId, participants: session.participants });
|
||||||
}
|
}
|
||||||
|
|
||||||
emitVideoCallUpdateToParticipants(session);
|
emitVideoCallUpdateToParticipants(session);
|
||||||
|
|||||||
Reference in New Issue
Block a user