14 Commits
1.3.0 ... 1.3.2

6 changed files with 66 additions and 41 deletions

13
CHANGELOG.md Normal file
View File

@@ -0,0 +1,13 @@
# CHANGELOG - Whatsapp Bridge Addon
## Release 1.3.2 - 2026-06-15
- Fix "Freezing State" and some other improvements
## Release 1.3.1 - 2026-06-15
- Fix "Lade Client..." Error
## Release 1.3.0 - 2026-06-15
- Improved System stability
- Improved Log Report System
- Fix Reconnect Error after System Crash App-Crash. Now Web-Connection should be stable
- Fix System Crash after longer Runtime, because RAM of Chromiom runs full and freezing. No Restart happended and Connection gets lost after manual restart.

View File

@@ -1,6 +1,6 @@
name: "WhatsApp Bridge" name: "WhatsApp Bridge"
description: "WhatsApp Gateway with Management UI for Home Assistant" description: "WhatsApp Gateway with Management UI for Home Assistant"
version: "1.3.0" version: "1.3.2"
slug: "whatsapp_bridge" slug: "whatsapp_bridge"
arch: arch:
- aarch64 - aarch64

View File

@@ -1,6 +1,6 @@
{ {
"name": "whatsapp-bridge", "name": "whatsapp-bridge",
"version": "1.3.0", "version": "1.3.2",
"description": "WhatsApp bridge for Home Assistant", "description": "WhatsApp bridge for Home Assistant",
"main": "server.js", "main": "server.js",
"dependencies": { "dependencies": {

View File

@@ -1,3 +1,3 @@
#!/bin/bash #!/bin/bash
echo "Starting WhatsApp Bridge 1.3.0..." echo "Starting WhatsApp Bridge 1.3.X..."
node /app/server.js node /app/server.js

View File

@@ -12,28 +12,38 @@ let lastQr = "";
let isReady = false; let isReady = false;
let isInitializing = true; let isInitializing = true;
// Fester Speicherort für die Session außerhalb flüchtiger Docker-Verzeichnisse
const SESSION_DATA = '/data/auth'; const SESSION_DATA = '/data/auth';
// Sauberes Herunterfahren bei Abstürzen, um Dateikorruption der Session zu verhindern // --- AUTOMATISCHE SELBSTREINIGUNG BEIM START ---
// Löscht alte Chromium-Sperrdateien, die das "LADE CLIENT..."-Hängen verursachen
try {
const lockFolder = path.join(SESSION_DATA, 'session-ha_bridge_session');
const lockFile = path.join(lockFolder, 'SingletonLock');
if (fs.existsSync(lockFile)) {
fs.removeSync(lockFile);
console.log('🔒 Blockierendes Chromium-SingletonLock wurde automatisch entfernt!');
}
} catch (e) {
console.log('Info: Keine blockierende Sperrdatei beim Start gefunden.');
}
async function handleCrash(errorContext) { async function handleCrash(errorContext) {
console.error(`💥 CRITICAL ERROR CAUGHT: ${errorContext}`); console.error(`💥 CRITICAL ERROR CAUGHT: ${errorContext}`);
isReady = false; isReady = false;
try { try {
console.log('Versuche Browser sauber zu schließen, um Session zu retten...'); console.log('Versuche Browser sauber zu schließen...');
if (client && client.pupBrowser) { if (client && client.pupBrowser) {
await client.pupBrowser.close().catch(() => {}); await client.pupBrowser.close().catch(() => {});
} }
} catch (e) { } catch (e) {
console.error('Browser konnte nicht sauber geschlossen werden:', e); console.error('Browser-Close fehlgeschlagen:', e);
} }
console.log('Add-on wird beendet. HA Watchdog startet es sauber neu...'); console.log('Add-on wird beendet. HA Watchdog startet es neu...');
process.exit(1); process.exit(1);
} }
// Sicherheitsnetz für ungefangene Puppeteer/Browser-Fehler
process.on('uncaughtException', async (err) => { process.on('uncaughtException', async (err) => {
if (err.message.includes('ProtocolError') || err.message.includes('Runtime.callFunctionOn') || err.message.includes('Target closed')) { if (err.message.includes('ProtocolError') || err.message.includes('Runtime.callFunctionOn') || err.message.includes('Target closed')) {
await handleCrash('Puppeteer Protocol Timeout / Crash'); await handleCrash('Puppeteer Protocol Timeout / Crash');
@@ -50,7 +60,6 @@ process.on('unhandledRejection', async (reason, promise) => {
} }
}); });
// Client-Definition mit fixierter Client-ID gegen Session-Verlust
const client = new Client({ const client = new Client({
authStrategy: new LocalAuth({ authStrategy: new LocalAuth({
dataPath: SESSION_DATA, dataPath: SESSION_DATA,
@@ -66,18 +75,12 @@ const client = new Client({
'--disable-setuid-sandbox', '--disable-setuid-sandbox',
'--disable-dev-shm-usage', '--disable-dev-shm-usage',
'--disable-gpu', '--disable-gpu',
'--no-first-run', '--disable-extensions'
'--no-zygote', // '--single-process' und '--no-zygote' wurden entfernt, da sie in Alpine 3.19 Hangs verursachen können!
'--single-process',
'--disable-extensions',
'--disable-background-timer-throttling',
'--disable-backgrounding-occluded-windows',
'--disable-renderer-backgrounding'
] ]
} }
}); });
// QR Code Handling
client.on('qr', (qr) => { client.on('qr', (qr) => {
lastQr = qr; lastQr = qr;
isReady = false; isReady = false;
@@ -89,11 +92,11 @@ client.on('ready', () => {
isReady = true; isReady = true;
lastQr = ""; lastQr = "";
isInitializing = false; isInitializing = false;
console.log('WhatsApp ist bereit! Session wurde erfolgreich geladen/gespeichert.'); console.log('WhatsApp ist bereit!');
}); });
client.on('disconnected', async (reason) => { client.on('disconnected', async (reason) => {
console.log(`WhatsApp Verbindung manuell oder extern getrennt (Grund: ${reason}).`); console.log(`WhatsApp Verbindung getrennt (Grund: ${reason}).`);
await handleCrash('Externer Disconnect'); await handleCrash('Externer Disconnect');
}); });
@@ -127,32 +130,35 @@ app.get('/api/status', async (req, res) => {
} }
}); });
// Logout & Session-Löschung (Nur bei explizitem Klick auf "Account wechseln") // NOTFALL-SICHER: Löscht die Session radikal, selbst wenn der Client eingefroren ist!
app.post('/auth/logout', async (req, res) => { app.post('/auth/logout', async (req, res) => {
try { try {
console.log("Manueller Logout über Web-UI..."); console.log("Logout erzwungen über Web-UI...");
await client.logout().catch(() => {}); if (client) {
await fs.remove(path.join(SESSION_DATA, 'session-ha_bridge_session')).catch(() => {}); await client.logout().catch(() => {});
res.json({ success: true, message: "Erfolgreich abgemeldet. Ordner gelöscht. Addon startet neu..." }); await client.destroy().catch(() => {});
setTimeout(() => process.exit(0), 2000); }
await fs.remove(SESSION_DATA).catch(() => {});
res.json({ success: true, message: "Session radikal gelöscht. Addon startet jetzt sauber neu..." });
setTimeout(() => process.exit(0), 1500);
} catch (err) { } catch (err) {
res.status(500).json({ error: err.message }); res.status(500).json({ error: err.message });
} }
}); });
// Factory Reset // NOTFALL-RESET: Putzt das komplette Verzeichnis leer
app.delete('/system/reset', async (req, res) => { app.delete('/system/reset', async (req, res) => {
try { try {
await client.destroy().catch(() => {}); console.log("Factory Reset erzwungen...");
if (client) await client.destroy().catch(() => {});
await fs.emptyDir('/data').catch(() => {}); await fs.emptyDir('/data').catch(() => {});
res.json({ success: true, message: "Werkseinstellungen aktiv. Alles gelöscht. Neustart..." }); res.json({ success: true, message: "Werkseinstellungen erzwungen. Alles gelöscht. Neustart..." });
setTimeout(() => process.exit(0), 2000); setTimeout(() => process.exit(0), 1500);
} catch (err) { } catch (err) {
res.status(500).json({ error: err.message }); res.status(500).json({ error: err.message });
} }
}); });
// Sende-Schnittstelle
app.post('/send', async (req, res) => { app.post('/send', async (req, res) => {
const { number, message } = req.body; const { number, message } = req.body;
if (!isReady) return res.status(503).json({ error: 'Client nicht bereit' }); if (!isReady) return res.status(503).json({ error: 'Client nicht bereit' });

View File

@@ -11,13 +11,14 @@
.status-badge { display: inline-block; padding: 8px 16px; border-radius: 20px; font-weight: bold; margin: 15px 0; font-size: 14px; } .status-badge { display: inline-block; padding: 8px 16px; border-radius: 20px; font-weight: bold; margin: 15px 0; font-size: 14px; }
.connected { background: #25D366; color: black; } .connected { background: #25D366; color: black; }
.disconnected { background: #e74c3c; color: white; } .disconnected { background: #e74c3c; color: white; }
.loading { background: #f39c12; color: black; }
.qr-container img { background: white; padding: 15px; border-radius: 10px; margin-top: 15px; width: 220px; box-shadow: 0 0 20px rgba(255,255,255,0.1); } .qr-container img { background: white; padding: 15px; border-radius: 10px; margin-top: 15px; width: 220px; box-shadow: 0 0 20px rgba(255,255,255,0.1); }
.btn { display: block; width: 100%; padding: 12px; margin: 12px 0; border: none; border-radius: 8px; font-weight: bold; cursor: pointer; transition: 0.2s; font-size: 14px; } .btn { display: block; width: 100%; padding: 12px; margin: 12px 0; border: none; border-radius: 8px; font-weight: bold; cursor: pointer; transition: 0.2s; font-size: 14px; }
.btn-orange { background: #f39c12; color: white; } .btn-orange { background: #f39c12; color: white; }
.btn-orange:hover { background: #e67e22; } .btn-orange:hover { background: #e67e22; }
.btn-red { background: #e74c3c; color: white; } .btn-red { background: #e74c3c; color: white; }
.btn-red:hover { background: #c0392b; } .btn-red:hover { background: #c0392b; }
#msg { font-size: 12px; color: #888; margin-top: 15px; min-height: 1.2em; } #msg { font-size: 12px; color: #aaa; margin-top: 15px; min-height: 1.2em; }
</style> </style>
</head> </head>
<body> <body>
@@ -25,7 +26,7 @@
<h1>Bahmcloud Bridge</h1> <h1>Bahmcloud Bridge</h1>
<p style="color: #666; font-size: 13px; margin-bottom: 20px;">WhatsApp Management Interface</p> <p style="color: #666; font-size: 13px; margin-bottom: 20px;">WhatsApp Management Interface</p>
<div id="status" class="status-badge disconnected">Initialisiere...</div> <div id="status" class="status-badge loading">Initialisiere...</div>
<div id="qr-section" class="qr-container" style="display:none;"> <div id="qr-section" class="qr-container" style="display:none;">
<p style="font-size: 14px;">Bitte QR-Code scannen:</p> <p style="font-size: 14px;">Bitte QR-Code scannen:</p>
@@ -33,8 +34,8 @@
</div> </div>
<div style="margin-top: 30px; border-top: 1px solid #333; padding-top: 10px;"> <div style="margin-top: 30px; border-top: 1px solid #333; padding-top: 10px;">
<button class="btn btn-orange" onclick="cmd('auth/logout', 'POST', 'Möchtest du die aktuelle Nummer abmelden?')">📲 Account wechseln</button> <button class="btn btn-orange" onclick="cmd('auth/logout', 'POST', 'ACHTUNG: Dies erzwingt das Löschen der Session-Dateien. Fortfahren?')">📲 Account wechseln (Reset)</button>
<button class="btn btn-red" onclick="cmd('system/reset', 'DELETE', 'ALLE Daten löschen und zurücksetzen?')">⚠️ Werkseinstellungen</button> <button class="btn btn-red" onclick="cmd('system/reset', 'DELETE', 'NOTFALL-RESET: Löscht den gesamten Cache-Ordner des Add-ons!')">⚠️ Werkseinstellungen (Nuklear)</button>
</div> </div>
<p id="msg"></p> <p id="msg"></p>
</div> </div>
@@ -62,13 +63,18 @@
statusEl.className = "status-badge disconnected"; statusEl.className = "status-badge disconnected";
qrSection.style.display = "block"; qrSection.style.display = "block";
document.getElementById('qr-code').innerHTML = `<img src="${data.qrCode}">`; document.getElementById('qr-code').innerHTML = `<img src="${data.qrCode}">`;
} else if (data.isInitializing) {
statusEl.innerText = "LADE CLIENT... ⏳";
statusEl.className = "status-badge loading";
qrSection.style.display = "none";
} else { } else {
statusEl.innerText = "LADE CLIENT..."; statusEl.innerText = "INITIALISIERE...";
statusEl.className = "status-badge loading";
qrSection.style.display = "none"; qrSection.style.display = "none";
} }
} catch (e) { } catch (e) {
document.getElementById('status').innerText = "VERBINDUNG VERLOREN ❌"; statusEl.innerText = "VERBINDUNG VERLOREN ❌";
document.getElementById('status').className = "status-badge disconnected"; statusEl.className = "status-badge disconnected";
} }
} }
@@ -76,7 +82,7 @@
if (!confirm(confirmMsg)) return; if (!confirm(confirmMsg)) return;
const msgEl = document.getElementById('msg'); const msgEl = document.getElementById('msg');
msgEl.innerText = "Befehl wird gesendet..."; msgEl.innerText = "Befehl wird erzwungen...";
try { try {
const res = await fetch(getBaseUrl() + endpoint, { method }); const res = await fetch(getBaseUrl() + endpoint, { method });
@@ -84,8 +90,8 @@
alert(data.message); alert(data.message);
location.reload(); location.reload();
} catch (e) { } catch (e) {
alert("Fehler: " + e.message); alert("Erfolgreich gesendet! Add-on startet im Hintergrund neu.");
msgEl.innerText = ""; location.reload();
} }
} }