fix(diagnostic): chat_history-Render verträgt kaputte Bubbles + EHOSTUNREACH skipped TLS-Fallback
Zwei kleine Robustness-Verbesserungen: 1) chat_history-Handler im Frontend: jede Bubble jetzt in try/catch. Wenn eine Bubble bei der Render-Pipeline (escape/linkify/regex-replace) eine Exception wirft, brach die ganze for-Schleife ab und alle nachfolgenden Bubbles wurden nicht mehr in den DOM geschrieben — beim Reload sah man dann nur die ersten N Eintraege und Stefan dachte die letzten Antworten waeren weg. Jetzt: Fehler-Bubble mit "⚠ Render-Fehler" + console.error, restliche Bubbles laufen weiter durch. 2) Diagnostic-Server RVS-Reconnect: TLS-Fallback war auch bei reinen Netz-Fehlern (EHOSTUNREACH, ECONNREFUSED, ENETUNREACH, ETIMEDOUT, ENOTFOUND, EAI_AGAIN) gefeuert — bringt nichts weil der Server eh tot ist, generiert aber doppelte Reconnect-Versuche + Log-Spam. Jetzt nur noch bei wirklichen TLS/Handshake-Fehlern. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+43
-25
@@ -1650,36 +1650,54 @@
|
|||||||
if (msg.type === 'chat_history') {
|
if (msg.type === 'chat_history') {
|
||||||
const boxes = [chatBox, document.getElementById('chat-box-fs')].filter(Boolean);
|
const boxes = [chatBox, document.getElementById('chat-box-fs')].filter(Boolean);
|
||||||
for (const b of boxes) b.innerHTML = '';
|
for (const b of boxes) b.innerHTML = '';
|
||||||
|
let errorCount = 0;
|
||||||
if (msg.messages && msg.messages.length > 0) {
|
if (msg.messages && msg.messages.length > 0) {
|
||||||
for (const m of msg.messages) {
|
for (let mi = 0; mi < msg.messages.length; mi++) {
|
||||||
if (m.type === 'aria_file') {
|
const m = msg.messages[mi];
|
||||||
// ARIA-Datei-Bubble — addAriaFile schreibt selbst in beide Boxen
|
try {
|
||||||
addAriaFile({ serverPath: m.serverPath, name: m.name, mimeType: m.mimeType, size: m.size, deleted: m.deleted });
|
if (m.type === 'aria_file') {
|
||||||
continue;
|
addAriaFile({ serverPath: m.serverPath, name: m.name, mimeType: m.mimeType, size: m.size, deleted: m.deleted });
|
||||||
}
|
continue;
|
||||||
// [FILE: ...]-Marker rausfiltern (gleicher Filter wie addChat)
|
}
|
||||||
const cleaned = (m.text || '').replace(/\[FILE:\s*\/shared\/uploads\/[^\]]+\]/gi, '').replace(/\n{3,}/g, '\n\n').trim();
|
const cleaned = (m.text || '').replace(/\[FILE:\s*\/shared\/uploads\/[^\]]+\]/gi, '').replace(/\n{3,}/g, '\n\n').trim();
|
||||||
const escaped = escapeHtml(cleaned);
|
const escaped = escapeHtml(cleaned);
|
||||||
let linked = linkifyText(escaped);
|
let linked = linkifyText(escaped);
|
||||||
// /shared/uploads/-Bildpfade auch im History inline rendern
|
linked = linked.replace(/\/shared\/uploads\/[^\s<"]+\.(jpg|jpeg|png|gif|webp|svg|bmp)/gi, (match) => {
|
||||||
linked = linked.replace(/\/shared\/uploads\/[^\s<"]+\.(jpg|jpeg|png|gif|webp|svg|bmp)/gi, (match) => {
|
return `<a href="${match}" target="_blank">${match}</a><img src="${match}" class="chat-media" onclick="openLightbox('image','${match}')" onerror="this.style.display='none'">`;
|
||||||
return `<a href="${match}" target="_blank">${match}</a><img src="${match}" class="chat-media" onclick="openLightbox('image','${match}')" onerror="this.style.display='none'">`;
|
});
|
||||||
});
|
const time = m.ts ? new Date(m.ts).toLocaleTimeString('de-DE') : '?';
|
||||||
const time = m.ts ? new Date(m.ts).toLocaleTimeString('de-DE') : '?';
|
const trashBtn = m.ts
|
||||||
const trashBtn = m.ts
|
? `<button class="bubble-trash" title="Diese Bubble loeschen" onclick="deleteDiagBubble(${m.ts})">🗑</button>`
|
||||||
? `<button class="bubble-trash" title="Diese Bubble loeschen" onclick="deleteDiagBubble(${m.ts})">🗑</button>`
|
: '';
|
||||||
: '';
|
const innerHtml = `${trashBtn}${linked}<div class="meta">${escapeHtml(m.meta)} — ${time}</div>`;
|
||||||
const innerHtml = `${trashBtn}${linked}<div class="meta">${escapeHtml(m.meta)} — ${time}</div>`;
|
for (const b of boxes) {
|
||||||
for (const b of boxes) {
|
const el = document.createElement('div');
|
||||||
const el = document.createElement('div');
|
el.className = `chat-msg ${m.type}`;
|
||||||
el.className = `chat-msg ${m.type}`;
|
if (m.ts) el.dataset.ts = String(m.ts);
|
||||||
if (m.ts) el.dataset.ts = String(m.ts);
|
el.innerHTML = innerHtml;
|
||||||
el.innerHTML = innerHtml;
|
b.appendChild(el);
|
||||||
b.appendChild(el);
|
}
|
||||||
|
} catch (renderErr) {
|
||||||
|
// Eine kaputte Bubble darf nicht den Rest der History killen.
|
||||||
|
// Vorher passierte genau das: Frontend-Render bracht bei einer
|
||||||
|
// problematischen Antwort ab, alle nachfolgenden Nachrichten waren
|
||||||
|
// beim Reload weg. Jetzt: Fehler-Bubble einbauen + weitermachen.
|
||||||
|
errorCount++;
|
||||||
|
console.error('chat_history render error at idx ' + mi + ':', renderErr, m);
|
||||||
|
for (const b of boxes) {
|
||||||
|
const el = document.createElement('div');
|
||||||
|
el.className = `chat-msg ${m.type || 'received'}`;
|
||||||
|
if (m.ts) el.dataset.ts = String(m.ts);
|
||||||
|
el.innerHTML = `<span style="color:#FF6B6B;">⚠ Render-Fehler in Bubble (${escapeHtml(String(renderErr.message || renderErr))})</span><div class="meta">${m.ts ? new Date(m.ts).toLocaleTimeString('de-DE') : '?'}</div>`;
|
||||||
|
b.appendChild(el);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const b of boxes) b.scrollTop = b.scrollHeight;
|
for (const b of boxes) b.scrollTop = b.scrollHeight;
|
||||||
}
|
}
|
||||||
|
if (errorCount > 0) {
|
||||||
|
console.warn(`chat_history: ${errorCount} Bubble(s) konnten nicht gerendert werden`);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+10
-2
@@ -701,8 +701,16 @@ function connectRVS(forcePlain) {
|
|||||||
state.rvs.lastError = err.message;
|
state.rvs.lastError = err.message;
|
||||||
broadcastState();
|
broadcastState();
|
||||||
|
|
||||||
// TLS Fallback
|
// TLS-Fallback nur bei wirklichen TLS/Handshake-Fehlern.
|
||||||
if (useTls && RVS_TLS_FALLBACK === "true" && !fallbackTriggered) {
|
// Bei Netz-Problemen wie EHOSTUNREACH, ECONNREFUSED, ENETUNREACH,
|
||||||
|
// EAI_AGAIN ist der Server eh tot — Fallback bringt nichts ausser
|
||||||
|
// Log-Spam und doppelten Retries.
|
||||||
|
const netErr = (err.code || err.message || "").toString();
|
||||||
|
const isNetDown =
|
||||||
|
/^(EHOSTUNREACH|ECONNREFUSED|ENETUNREACH|ETIMEDOUT|EAI_AGAIN|ENOTFOUND)$/.test(netErr) ||
|
||||||
|
/EHOSTUNREACH|ECONNREFUSED|ENETUNREACH|ETIMEDOUT|EAI_AGAIN|ENOTFOUND/.test(err.message || "");
|
||||||
|
|
||||||
|
if (useTls && RVS_TLS_FALLBACK === "true" && !fallbackTriggered && !isNetDown) {
|
||||||
fallbackTriggered = true;
|
fallbackTriggered = true;
|
||||||
log("warn", "rvs", "TLS fehlgeschlagen — Fallback auf ws://");
|
log("warn", "rvs", "TLS fehlgeschlagen — Fallback auf ws://");
|
||||||
try { ws.removeAllListeners(); ws.close(); } catch (_) {}
|
try { ws.removeAllListeners(); ws.close(); } catch (_) {}
|
||||||
|
|||||||
Reference in New Issue
Block a user