diff --git a/route.php b/route.php
new file mode 100644
index 0000000..9a74408
--- /dev/null
+++ b/route.php
@@ -0,0 +1,469 @@
+&1';
+ $rawOutput = shell_exec($command);
+ if ($rawOutput === null) {
+ $error = 'Traceroute konnte nicht ausgeführt werden. Ist das Kommando verfügbar?';
+ } else {
+ $traceData = parseTraceroute($rawOutput);
+ if (empty($traceData)) {
+ $error = 'Keine Hops gefunden. Prüfen Sie den Hostnamen oder versuchen Sie es später erneut.';
+ }
+ }
+ }
+}
+
+if (empty($traceData)) {
+ $traceData = getSampleTrace();
+ if ($error === '') {
+ $error = 'Es werden Beispieldaten angezeigt. Starten Sie eine Abfrage, um echte Traceroute-Daten zu sehen.';
+ }
+}
+
+function parseTraceroute(string $raw): array
+{
+ $lines = preg_split('/\r?\n/', trim($raw));
+ if (!$lines) {
+ return [];
+ }
+
+ $hops = [];
+ foreach ($lines as $line) {
+ if (preg_match('/^\s*\d+\s+/', $line) !== 1) {
+ continue;
+ }
+
+ preg_match_all('/(\d+\.\d+)\s+ms/', $line, $latencyMatches);
+ $latencies = array_map('floatval', $latencyMatches[1] ?? []);
+ $avgLatency = !empty($latencies) ? array_sum($latencies) / count($latencies) : null;
+
+ if (preg_match('/^\s*(\d+)\s+([0-9\.\*]+)/', $line, $parts) !== 1) {
+ continue;
+ }
+
+ $hopNumber = (int) $parts[1];
+ $ip = $parts[2];
+ if ($ip === '*') {
+ $ip = 'Zeitüberschreitung';
+ }
+
+ $hops[] = [
+ 'hop' => $hopNumber,
+ 'ip' => $ip,
+ 'avgLatency' => $avgLatency,
+ 'raw' => trim($line),
+ ];
+ }
+
+ return $hops;
+}
+
+function getSampleTrace(): array
+{
+ return [
+ ['hop' => 1, 'ip' => '192.168.0.1', 'avgLatency' => 1.2, 'raw' => '1 192.168.0.1 1.123 ms 1.234 ms 1.301 ms'],
+ ['hop' => 2, 'ip' => '10.12.34.1', 'avgLatency' => 9.4, 'raw' => '2 10.12.34.1 9.123 ms 9.567 ms 9.400 ms'],
+ ['hop' => 3, 'ip' => '172.16.5.4', 'avgLatency' => 18.7, 'raw' => '3 172.16.5.4 18.432 ms 18.913 ms 18.787 ms'],
+ ['hop' => 4, 'ip' => '203.0.113.5', 'avgLatency' => 32.9, 'raw' => '4 203.0.113.5 32.113 ms 33.441 ms 33.212 ms'],
+ ['hop' => 5, 'ip' => '93.184.216.34', 'avgLatency' => 48.2, 'raw' => '5 93.184.216.34 48.112 ms 48.501 ms 48.032 ms'],
+ ];
+}
+
+function generatePositions(array $trace): array
+{
+ $positions = [];
+ $radius = 25;
+ $spacing = 10;
+ foreach ($trace as $index => $hop) {
+ $angle = $index * 0.9;
+ $positions[] = [
+ 'x' => cos($angle) * $radius,
+ 'y' => $index * $spacing,
+ 'z' => sin($angle) * $radius,
+ ];
+ }
+ return $positions;
+}
+
+$positions = generatePositions($traceData);
+?>
+
+
+
+
+
+ 3D Traceroute Visualisierung
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/write.php b/write.php
index 3824f03..28addb9 100644
--- a/write.php
+++ b/write.php
@@ -30,8 +30,6 @@ define('LOG_RETENTION_MONTHS', 6);
define('ONLINE_TIMEOUT_SECONDS', 30);
define('SSE_RETRY_MS', 500);
define('MAX_MESSAGES_PER_FETCH', 200);
-define('MAX_ATTACHMENT_SIZE', 200 * 1024); // 200 KB
-define('UPLOAD_DIR', __DIR__ . '/uploads');
// Rate Limiting
define('MAX_MESSAGES_PER_MINUTE', 10);
@@ -73,152 +71,129 @@ $PROFANITY_FILTER = [
// ═══════════════════════════════════════════════════════════
function getDB() {
- static $db = null;
- static $initialized = false;
+ $db = new SQLite3(DB_FILE);
+ $db->busyTimeout(5000);
+
+ // Users Table (mit Geburtsdatum und User-ID)
+ $db->exec('
+ CREATE TABLE IF NOT EXISTS users (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ username TEXT NOT NULL,
+ user_id TEXT UNIQUE NOT NULL,
+ birthdate DATE NOT NULL,
+ age_group TEXT NOT NULL,
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+ last_seen DATETIME DEFAULT CURRENT_TIMESTAMP,
+ is_banned INTEGER DEFAULT 0,
+ ban_reason TEXT
+ )
+ ');
+
+ // Messages Table
+ $db->exec('
+ CREATE TABLE IF NOT EXISTS messages (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ from_user_id INTEGER NOT NULL,
+ to_user_id INTEGER NOT NULL,
+ message TEXT NOT NULL,
+ timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
+ is_read INTEGER DEFAULT 0,
+ is_flagged INTEGER DEFAULT 0,
+ flag_reason TEXT,
+ FOREIGN KEY (from_user_id) REFERENCES users(id),
+ FOREIGN KEY (to_user_id) REFERENCES users(id)
+ )
+ ');
+
+ // Online Status Table
+ $db->exec('
+ CREATE TABLE IF NOT EXISTS online_status (
+ user_id INTEGER PRIMARY KEY,
+ last_ping DATETIME DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (user_id) REFERENCES users(id)
+ )
+ ');
- if ($db === null) {
- if (!is_dir(UPLOAD_DIR)) {
- @mkdir(UPLOAD_DIR, 0755, true);
- }
+ // User Sessions Table
+ $db->exec('
+ CREATE TABLE IF NOT EXISTS user_sessions (
+ user_id INTEGER PRIMARY KEY,
+ session_token TEXT NOT NULL,
+ last_seen DATETIME DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (user_id) REFERENCES users(id)
+ )
+ ');
- $db = new SQLite3(DB_FILE);
- $db->busyTimeout(5000);
- }
-
- if (!$initialized) {
- // Users Table (mit Geburtsdatum und User-ID)
- $db->exec('
- CREATE TABLE IF NOT EXISTS users (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- username TEXT NOT NULL,
- user_id TEXT UNIQUE NOT NULL,
- birthdate DATE NOT NULL,
- age_group TEXT NOT NULL,
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- last_seen DATETIME DEFAULT CURRENT_TIMESTAMP,
- is_banned INTEGER DEFAULT 0,
- ban_reason TEXT
- )
- ');
-
- // Messages Table
- $db->exec('
- CREATE TABLE IF NOT EXISTS messages (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- from_user_id INTEGER NOT NULL,
- to_user_id INTEGER NOT NULL,
- message TEXT NOT NULL,
- timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
- is_read INTEGER DEFAULT 0,
- is_flagged INTEGER DEFAULT 0,
- flag_reason TEXT,
- attachment_path TEXT,
- attachment_type TEXT,
- attachment_size INTEGER,
- FOREIGN KEY (from_user_id) REFERENCES users(id),
- FOREIGN KEY (to_user_id) REFERENCES users(id)
- )
- ');
-
- // Ensure attachment columns exist for older installations
- $messagesInfo = $db->query('PRAGMA table_info(messages)');
- $messageColumns = [];
- while ($column = $messagesInfo->fetchArray(SQLITE3_ASSOC)) {
- $messageColumns[$column['name']] = true;
- }
- if (!isset($messageColumns['attachment_path'])) {
- $db->exec('ALTER TABLE messages ADD COLUMN attachment_path TEXT');
- }
- if (!isset($messageColumns['attachment_type'])) {
- $db->exec('ALTER TABLE messages ADD COLUMN attachment_type TEXT');
- }
- if (!isset($messageColumns['attachment_size'])) {
- $db->exec('ALTER TABLE messages ADD COLUMN attachment_size INTEGER');
- }
-
- // Online Status Table
- $db->exec('
- CREATE TABLE IF NOT EXISTS online_status (
- user_id INTEGER PRIMARY KEY,
- last_ping DATETIME DEFAULT CURRENT_TIMESTAMP,
- FOREIGN KEY (user_id) REFERENCES users(id)
- )
- ');
-
- // User Sessions Table
- $db->exec('
- CREATE TABLE IF NOT EXISTS user_sessions (
- user_id INTEGER PRIMARY KEY,
- session_token TEXT NOT NULL,
- last_seen DATETIME DEFAULT CURRENT_TIMESTAMP,
- FOREIGN KEY (user_id) REFERENCES users(id)
- )
- ');
-
- // Reports Table
- $db->exec('
- CREATE TABLE IF NOT EXISTS reports (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- reporter_id INTEGER NOT NULL,
- reported_user_id INTEGER NOT NULL,
- reason TEXT NOT NULL,
- message_id INTEGER,
- timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
- status TEXT DEFAULT "pending",
- FOREIGN KEY (reporter_id) REFERENCES users(id),
- FOREIGN KEY (reported_user_id) REFERENCES users(id),
- FOREIGN KEY (message_id) REFERENCES messages(id)
- )
- ');
-
- // Blocks Table
- $db->exec('
- CREATE TABLE IF NOT EXISTS blocks (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- blocker_id INTEGER NOT NULL,
- blocked_id INTEGER NOT NULL,
- timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
- FOREIGN KEY (blocker_id) REFERENCES users(id),
- FOREIGN KEY (blocked_id) REFERENCES users(id),
- UNIQUE(blocker_id, blocked_id)
- )
- ');
-
- // Rate Limiting Table
- $db->exec('
- CREATE TABLE IF NOT EXISTS rate_limits (
- user_id INTEGER NOT NULL,
- action_type TEXT NOT NULL,
- timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
- FOREIGN KEY (user_id) REFERENCES users(id)
- )
- ');
-
- // Logs Table (für Behörden)
- $db->exec('
- CREATE TABLE IF NOT EXISTS security_logs (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- user_id INTEGER,
- action TEXT NOT NULL,
- details TEXT,
- ip_address TEXT,
- timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
- FOREIGN KEY (user_id) REFERENCES users(id)
- )
- ');
-
- // Admin Table
- $db->exec('
- CREATE TABLE IF NOT EXISTS admins (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- username TEXT UNIQUE NOT NULL,
- password_hash TEXT NOT NULL,
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP
- )
- ');
-
- // Create default admin if not exists
- $stmt = $db->prepare('SELECT COUNT(*) as count FROM admins WHERE username = :username');
+ // Reports Table
+ $db->exec('
+ CREATE TABLE IF NOT EXISTS reports (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ reporter_id INTEGER NOT NULL,
+ reported_user_id INTEGER NOT NULL,
+ reason TEXT NOT NULL,
+ message_id INTEGER,
+ timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
+ status TEXT DEFAULT "pending",
+ FOREIGN KEY (reporter_id) REFERENCES users(id),
+ FOREIGN KEY (reported_user_id) REFERENCES users(id),
+ FOREIGN KEY (message_id) REFERENCES messages(id)
+ )
+ ');
+
+ // Blocks Table
+ $db->exec('
+ CREATE TABLE IF NOT EXISTS blocks (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ blocker_id INTEGER NOT NULL,
+ blocked_id INTEGER NOT NULL,
+ timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (blocker_id) REFERENCES users(id),
+ FOREIGN KEY (blocked_id) REFERENCES users(id),
+ UNIQUE(blocker_id, blocked_id)
+ )
+ ');
+
+ // Rate Limiting Table
+ $db->exec('
+ CREATE TABLE IF NOT EXISTS rate_limits (
+ user_id INTEGER NOT NULL,
+ action_type TEXT NOT NULL,
+ timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (user_id) REFERENCES users(id)
+ )
+ ');
+
+ // Logs Table (für Behörden)
+ $db->exec('
+ CREATE TABLE IF NOT EXISTS security_logs (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ user_id INTEGER,
+ action TEXT NOT NULL,
+ details TEXT,
+ ip_address TEXT,
+ timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (user_id) REFERENCES users(id)
+ )
+ ');
+
+ // Admin Table
+ $db->exec('
+ CREATE TABLE IF NOT EXISTS admins (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ username TEXT UNIQUE NOT NULL,
+ password_hash TEXT NOT NULL,
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
+ )
+ ');
+
+ // Create default admin if not exists
+ $stmt = $db->prepare('SELECT COUNT(*) as count FROM admins WHERE username = :username');
+ $stmt->bindValue(':username', ADMIN_USERNAME, SQLITE3_TEXT);
+ $result = $stmt->execute();
+ $row = $result->fetchArray(SQLITE3_ASSOC);
+
+ if ($row['count'] == 0) {
+ $stmt = $db->prepare('INSERT INTO admins (username, password_hash) VALUES (:username, :password)');
$stmt->bindValue(':username', ADMIN_USERNAME, SQLITE3_TEXT);
$result = $stmt->execute();
$row = $result->fetchArray(SQLITE3_ASSOC);
@@ -240,6 +215,14 @@ function getDB() {
$initialized = true;
}
+
+ // Create indexes
+ $db->exec('CREATE INDEX IF NOT EXISTS idx_messages_users ON messages(from_user_id, to_user_id)');
+ $db->exec('CREATE INDEX IF NOT EXISTS idx_messages_timestamp ON messages(timestamp)');
+ $db->exec('CREATE INDEX IF NOT EXISTS idx_users_age_group ON users(age_group)');
+ $db->exec('CREATE INDEX IF NOT EXISTS idx_blocks ON blocks(blocker_id, blocked_id)');
+ $db->exec('CREATE INDEX IF NOT EXISTS idx_reports_status ON reports(status)');
+ $db->exec('CREATE INDEX IF NOT EXISTS idx_user_sessions_last_seen ON user_sessions(last_seen)');
return $db;
}
@@ -503,7 +486,7 @@ function generateSessionToken() {
return bin2hex(random_bytes(32));
}
-function startUserSession($userId, $force = false) {
+function startUserSession($userId) {
if (!$userId) {
return ['allowed' => false, 'error' => 'Ungültige Benutzer-ID'];
}
@@ -514,24 +497,17 @@ function startUserSession($userId, $force = false) {
$result = $stmt->execute();
$existing = $result->fetchArray(SQLITE3_ASSOC);
- if ($existing && !$force && !empty($existing['last_seen'])) {
+ if ($existing && !empty($existing['last_seen'])) {
$secondsSinceLastSeen = time() - strtotime($existing['last_seen']);
if ($secondsSinceLastSeen < ONLINE_TIMEOUT_SECONDS) {
return [
'allowed' => false,
- 'error' => 'Du bist bereits auf einem anderen Gerät eingeloggt. Übernimm die Sitzung nur, wenn du wirklich ausgeloggt bist.',
- 'can_force' => true
+ 'error' => 'Du bist bereits auf einem anderen Gerät eingeloggt. Bitte dort zuerst ausloggen oder kurz warten.'
];
}
}
- if ($force && $existing) {
- $stmt = $db->prepare('DELETE FROM user_sessions WHERE user_id = :user_id');
- $stmt->bindValue(':user_id', $userId, SQLITE3_INTEGER);
- $stmt->execute();
- }
-
$token = generateSessionToken();
$stmt = $db->prepare('
@@ -1006,9 +982,6 @@ if (isset($_POST['action']) || isset($_GET['action'])) {
m.timestamp,
m.is_read,
m.is_flagged,
- m.attachment_path,
- m.attachment_type,
- m.attachment_size,
u.username as from_username,
u.user_id as from_display_id
FROM messages m
@@ -1959,7 +1932,6 @@ if (isset($_GET['stream']) && $_GET['stream'] === 'events') {
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
background: linear-gradient(135deg, #fef08a 0%, #f97316 100%);
- background-color: #fff9db;
min-height: 100vh;
display: flex;
align-items: center;
@@ -3647,136 +3619,6 @@ const chatStateMessageEl = document.getElementById('chatStateMessage');
const chatMessagesHeaderEl = document.getElementById('chatMessagesHeader');
const chatInputEl = document.getElementById('chatInput');
const sendButtonEl = document.getElementById('sendButton');
-const attachmentButtonEl = document.getElementById('attachmentButton');
-const attachmentInputEl = document.getElementById('attachmentInput');
-const attachmentInfoEl = document.getElementById('attachmentInfo');
-const attachmentFileNameEl = document.getElementById('attachmentFileName');
-const attachmentClearBtnEl = document.getElementById('attachmentClearBtn');
-const attachmentWarningEl = document.getElementById('attachmentWarning');
-const ATTACHMENT_MAX_SIZE = 200 * 1024;
-let messageAbortController = null;
-let sseErrorCount = 0;
-let usePollingFallback = false;
-let pollingTimerId = null;
-let isPollingUpdates = false;
-const POLLING_INTERVAL_MS = 5000;
-
-function processIncomingMessages(messages) {
- if (!Array.isArray(messages) || messages.length === 0) {
- return;
- }
-
- let shouldRender = false;
- const markReadFor = new Set();
-
- messages.forEach(msg => {
- const messageId = Number(msg.id);
- if (Number.isFinite(messageId) && messageId > state.lastMessageId) {
- state.lastMessageId = messageId;
- }
-
- const isRelevantChat = state.selectedUserId && (
- (msg.from_user_id === state.selectedUserId && msg.to_user_id === state.currentUserId) ||
- (msg.from_user_id === state.currentUserId && msg.to_user_id === state.selectedUserId)
- );
-
- if (isRelevantChat) {
- const alreadyExists = state.messages.some(existing => Number(existing.id) === messageId);
-
- if (!alreadyExists) {
- state.messages.push(msg);
- shouldRender = true;
-
- if (msg.to_user_id === state.currentUserId) {
- markReadFor.add(msg.from_user_id);
- }
- }
- }
- });
-
- if (shouldRender) {
- renderMessages();
- }
-
- markReadFor.forEach(userId => markAsRead(userId));
-
- if (messages.length > 0) {
- loadUsers();
- }
-}
-
-function stopPollingUpdates() {
- if (pollingTimerId) {
- clearInterval(pollingTimerId);
- pollingTimerId = null;
- }
-}
-
-function startPollingUpdates() {
- stopPollingUpdates();
- pollMessages();
- pollingTimerId = setInterval(pollMessages, POLLING_INTERVAL_MS);
-}
-
-async function pollMessages() {
- if (isPollingUpdates) {
- return;
- }
-
- isPollingUpdates = true;
-
- const formData = new FormData();
- formData.append('action', 'poll_updates');
- formData.append('last_message_id', state.lastMessageId);
-
- try {
- const response = await postFormData(formData);
- const result = await response.json();
-
- if (result && result.success) {
- const messages = Array.isArray(result.messages) ? result.messages : [];
- if (typeof result.last_message_id === 'number') {
- const newest = Number(result.last_message_id);
- if (Number.isFinite(newest)) {
- state.lastMessageId = Math.max(state.lastMessageId, newest);
- }
- }
- processIncomingMessages(messages);
- }
- } catch (error) {
- console.warn('Polling fehlgeschlagen:', error);
- } finally {
- isPollingUpdates = false;
- }
-}
-
-function enablePollingFallback() {
- if (usePollingFallback) {
- return;
- }
-
- usePollingFallback = true;
-
- if (state.eventSource) {
- state.eventSource.close();
- state.eventSource = null;
- }
-
- if (!state.connectionErrorShown && state.selectedUserId && !state.isLoadingMessages) {
- updateChatState('error', 'Live-Verbindung blockiert. Wechsel auf sichere Aktualisierung…');
- state.connectionErrorShown = true;
- }
-
- startPollingUpdates();
-}
-
-function startRealtime() {
- if (usePollingFallback) {
- startPollingUpdates();
- } else {
- startSSE();
- }
-}
async function loadUsers() {
if (!userListEl) {
@@ -3787,7 +3629,7 @@ async function loadUsers() {
renderUserList();
try {
- const response = await fetch(buildUrl({ action: 'get_users' }), { credentials: 'same-origin' });
+ const response = await fetch('?action=get_users');
if (!response.ok) {
throw new Error('NETZWERK_FEHLER');
}
@@ -3836,23 +3678,9 @@ function renderUserList() {
return;
}
- const offlineLimit = 5;
- const onlineUsers = [];
- const offlineUsers = [];
-
- filtered.forEach(user => {
- if (user.is_online) {
- onlineUsers.push(user);
- } else {
- offlineUsers.push(user);
- }
- });
-
- const limitedUsers = onlineUsers.concat(offlineUsers.slice(0, offlineLimit));
-
const fragment = document.createDocumentFragment();
- limitedUsers.forEach(user => {
+ filtered.forEach(user => {
const item = document.createElement('button');
item.type = 'button';
item.className = 'user-item' + (Number(user.id) === Number(state.selectedUserId) ? ' active' : '');
@@ -3900,15 +3728,6 @@ function renderUserList() {
userListEl.innerHTML = '';
userListEl.appendChild(fragment);
-
- if (offlineUsers.length > offlineLimit) {
- const hint = document.createElement('div');
- hint.className = 'user-status';
- hint.style.textAlign = 'center';
- hint.style.marginTop = '12px';
- hint.textContent = 'Weitere Offline-Nutzer werden ausgeblendet.';
- userListEl.appendChild(hint);
- }
}
function renderChatHeader(displayName) {
@@ -3966,9 +3785,6 @@ function selectUser(userId, displayName) {
state.selectedUserId = userId;
state.messages = [];
- clearAttachmentSelection();
- clearAttachmentWarning();
-
if (chatWelcomeEl) {
chatWelcomeEl.style.display = 'none';
}
@@ -3993,21 +3809,11 @@ async function loadMessages(userId) {
return;
}
- if (messageAbortController) {
- messageAbortController.abort();
- }
-
- const currentController = new AbortController();
- messageAbortController = currentController;
-
state.isLoadingMessages = true;
updateChatState('loading', 'Nachrichten werden geladen…');
try {
- const response = await fetch(buildUrl({ action: 'get_messages', user_id: userId }), {
- signal: currentController.signal,
- credentials: 'same-origin'
- });
+ const response = await fetch(`?action=get_messages&user_id=${userId}`);
if (!response.ok) {
throw new Error('NETZWERK_FEHLER');
}
@@ -4032,9 +3838,6 @@ async function loadMessages(userId) {
updateChatState('empty', 'Noch keine Nachrichten. Starte das Gespräch!');
}
} catch (error) {
- if (error && error.name === 'AbortError') {
- return;
- }
console.error('Nachrichten konnten nicht geladen werden:', error);
state.messages = [];
if (chatMessagesEl) {
@@ -4045,10 +3848,7 @@ async function loadMessages(userId) {
: 'Nachrichten konnten nicht geladen werden. Bitte versuche es erneut.';
updateChatState('error', errorMessage);
} finally {
- if (messageAbortController === currentController) {
- messageAbortController = null;
- state.isLoadingMessages = false;
- }
+ state.isLoadingMessages = false;
}
}
@@ -4089,51 +3889,12 @@ function renderMessages() {
updateChatState(null);
}
-function clearAttachmentSelection() {
- if (attachmentInputEl) {
- attachmentInputEl.value = '';
- }
- if (attachmentInfoEl) {
- attachmentInfoEl.classList.add('hidden');
- }
- if (attachmentFileNameEl) {
- attachmentFileNameEl.textContent = '';
- }
-}
-
-function showAttachmentWarning(message) {
- if (attachmentWarningEl) {
- attachmentWarningEl.textContent = message;
- attachmentWarningEl.classList.remove('hidden');
- } else {
- alert(message);
- }
-}
-
-function clearAttachmentWarning() {
- if (attachmentWarningEl) {
- attachmentWarningEl.textContent = '';
- attachmentWarningEl.classList.add('hidden');
- }
-}
-
async function sendMessage() {
if (!chatInputEl) {
return;
}
- if (!state.selectedUserId) {
- showAttachmentWarning('Bitte wähle zuerst einen Chat aus.');
- return;
- }
-
const message = chatInputEl.value.trim();
- const attachmentFile = attachmentInputEl?.files?.[0] || null;
-
- if (!message && !attachmentFile) {
- showAttachmentWarning('Bitte gib eine Nachricht ein oder hänge ein JPG-Bild an.');
- return;
- }
clearAttachmentWarning();
@@ -4164,21 +3925,11 @@ async function sendMessage() {
formData.append('attachment', attachmentFile);
}
- try {
- const response = await postFormData(formData);
- const result = await response.json();
-
- if (result.success) {
- chatInputEl.value = '';
- chatInputEl.dispatchEvent(new Event('input'));
- clearAttachmentSelection();
- clearAttachmentWarning();
- } else {
- showAttachmentWarning(result.error || 'Nachricht konnte nicht gesendet werden.');
- }
- } catch (error) {
- console.error('Nachricht konnte nicht gesendet werden:', error);
- showAttachmentWarning('Nachricht konnte nicht gesendet werden.');
+ if (result.success) {
+ chatInputEl.value = '';
+ chatInputEl.dispatchEvent(new Event('input'));
+ } else {
+ alert(result.error);
}
}
@@ -4192,25 +3943,14 @@ async function markAsRead(userId) {
}
function startSSE() {
- stopPollingUpdates();
-
if (state.eventSource) {
state.eventSource.close();
- state.eventSource = null;
}
- const url = buildUrl({ stream: 'events', last_message_id: state.lastMessageId, t: Date.now() });
-
- try {
- state.eventSource = new EventSource(url);
- } catch (error) {
- console.warn('SSE kann nicht gestartet werden, wechsle auf Polling:', error);
- enablePollingFallback();
- return;
- }
+ const url = `?stream=events&last_message_id=${state.lastMessageId}&t=${Date.now()}`;
+ state.eventSource = new EventSource(url);
state.eventSource.onopen = () => {
- sseErrorCount = 0;
state.connectionErrorShown = false;
if (!state.selectedUserId) {
@@ -4230,10 +3970,30 @@ function startSSE() {
state.eventSource.onmessage = (event) => {
state.connectionErrorShown = false;
+ const data = JSON.parse(event.data);
- if (!event.data) {
- return;
- }
+ if (data.type === 'messages' && data.messages) {
+ data.messages.forEach(msg => {
+ const messageId = Number(msg.id);
+
+ if (messageId > state.lastMessageId) {
+ state.lastMessageId = messageId;
+
+ if (state.selectedUserId &&
+ ((msg.from_user_id === state.selectedUserId && msg.to_user_id === state.currentUserId) ||
+ (msg.from_user_id === state.currentUserId && msg.to_user_id === state.selectedUserId))) {
+
+ if (!state.messages.find(m => Number(m.id) === messageId)) {
+ state.messages.push(msg);
+ renderMessages();
+
+ if (msg.to_user_id === state.currentUserId) {
+ markAsRead(msg.from_user_id);
+ }
+ }
+ }
+ }
+ });
try {
const data = JSON.parse(event.data);
@@ -4246,13 +4006,6 @@ function startSSE() {
};
state.eventSource.onerror = () => {
- if (state.eventSource) {
- state.eventSource.close();
- state.eventSource = null;
- }
-
- sseErrorCount += 1;
-
if (!state.connectionErrorShown) {
state.connectionErrorShown = true;
console.warn('SSE-Verbindung unterbrochen, versuche Neuverbindung.');
@@ -4261,16 +4014,11 @@ function startSSE() {
}
}
- if (usePollingFallback || sseErrorCount >= 3) {
- enablePollingFallback();
- return;
+ if (state.eventSource) {
+ state.eventSource.close();
}
- setTimeout(() => {
- if (!usePollingFallback) {
- startSSE();
- }
- }, 500);
+ setTimeout(startSSE, 1500);
};
}
@@ -4280,56 +4028,6 @@ function escapeHtml(text) {
return div.innerHTML;
}
-function escapeAttribute(value) {
- const div = document.createElement('div');
- div.textContent = value ?? '';
- return div.innerHTML.replace(/"/g, '"').replace(/'/g, ''');
-}
-
-attachmentButtonEl?.addEventListener('click', () => {
- attachmentInputEl?.click();
-});
-
-attachmentInputEl?.addEventListener('change', () => {
- clearAttachmentWarning();
-
- if (!attachmentInputEl.files || attachmentInputEl.files.length === 0) {
- clearAttachmentSelection();
- return;
- }
-
- const file = attachmentInputEl.files[0];
- const fileType = (file.type || '').toLowerCase();
- const fileName = file.name || '';
- const isJpeg = /^image\/jpe?g$/.test(fileType) || /\.jpe?g$/i.test(fileName);
-
- if (!isJpeg) {
- showAttachmentWarning('Nur JPG-Bilder sind erlaubt.');
- clearAttachmentSelection();
- return;
- }
-
- if (file.size > ATTACHMENT_MAX_SIZE) {
- showAttachmentWarning('Bild ist zu groß (max. 200 KB).');
- clearAttachmentSelection();
- return;
- }
-
- if (attachmentInfoEl) {
- attachmentInfoEl.classList.remove('hidden');
- }
-
- if (attachmentFileNameEl) {
- const sizeKb = Math.max(1, Math.round(file.size / 1024));
- attachmentFileNameEl.textContent = `${file.name} (${sizeKb} KB)`;
- }
-});
-
-attachmentClearBtnEl?.addEventListener('click', () => {
- clearAttachmentSelection();
- clearAttachmentWarning();
-});
-
sendButtonEl?.addEventListener('click', sendMessage);
chatInputEl?.addEventListener('keypress', (e) => {