diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b98d00..a366ce2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ Format mengikuti [Keep a Changelog](https://keepachangelog.com/) dan [Semantic Versioning](https://semver.org/). +## [0.5.17] — 2026-11-06 — "gowa-connected" + +### Diperbaiki +- Status GOWA membaca array `/devices` (banyak device) sehingga tidak lagi selalu menampilkan **Belum Terhubung** ketika Device ID belum diisi. +- Device yang terhubung disimpan otomatis sebagai `gowa_device_id` — tidak perlu menyalin ID manual dari UI GOWA. +- Setelah scan QR, halaman memantau status tiap 3 detik; QR otomatis disembunyikan dan status berubah **Terhubung** saat berhasil. +- Pengiriman memilih device yang benar-benar terhubung bila ada lebih dari satu device. + ## [0.5.16] — 2026-11-05 — "restart-help" ### Ditambahkan diff --git a/README.md b/README.md index b747bc2..45893fc 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@

Lisensi: ISC - Versi 0.5.16 (restart-help) + Versi 0.5.17 (gowa-connected) Node.js >= 20 Docker Demo diff --git a/package.json b/package.json index 947ca92..9a72a9e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "open-isp", - "version": "0.5.16", + "version": "0.5.17", "description": "OPEN-ISP \u2014 sistem billing & manajemen ISP open-source (PWA pelanggan, multi-OLT, RADIUS, WhatsApp, QRIS)", "main": "app-customer.js", "scripts": { diff --git a/routes/adminPortal.js b/routes/adminPortal.js index 4880c79..6f74bad 100644 --- a/routes/adminPortal.js +++ b/routes/adminPortal.js @@ -7388,11 +7388,27 @@ router.get('/api/whatsapp/status', requireAdmin, async (req, res) => { } else if (gatewayType === 'gowa') { let st = null; try { st = await require('../services/gowaWhatsappService').gowaStatus(); } catch (e) { st = { ok: false, error: e.message }; } - const rr = (st && st.data && st.data.results) || {}; - const loggedIn = !!(rr.is_logged_in || rr.isConnected || rr.connected); - const isConn = !!(rr.is_connected || rr.connected); + // /devices mengembalikan array (banyak device); /devices/:id/status mengembalikan objek. + const raw = st && st.data && st.data.results; + const list = Array.isArray(raw) ? raw : ((raw && typeof raw === 'object') ? [raw] : []); + const pick = list.find(function (d) { + return d && (d.is_connected || d.is_logged_in || d.connected || + d.state === 'connected' || d.state === 'logged_in'); + }) || list[0] || {}; + const loggedIn = !!(pick.is_logged_in || pick.is_connected || pick.connected || + pick.state === 'connected' || pick.state === 'logged_in'); + const isConn = !!(pick.is_connected || pick.connected || pick.state === 'connected'); const conn = isConn ? 'open' : (loggedIn ? 'connecting' : 'close'); - res.json({ connection: conn, gateway: 'gowa', loggedIn: loggedIn, user: { id: 'gowa_gateway', name: loggedIn ? 'GOWA (WhatsApp)' : 'GOWA Gateway' }, lastUpdate: new Date().toISOString() }); + const deviceId = String(pick.device_id || pick.id || '').trim(); + // Simpan otomatis device id agar status berikutnya memakai /devices/:id/status. + if (deviceId && !String(getSetting('gowa_device_id', '') || '').trim()) { + try { saveSettings({ gowa_device_id: deviceId }); } catch (e) {} + } + res.json({ + connection: conn, gateway: 'gowa', loggedIn: loggedIn, deviceId: deviceId, + user: { id: deviceId || 'gowa_gateway', name: loggedIn ? 'GOWA (WhatsApp)' : 'GOWA Gateway' }, + lastUpdate: new Date().toISOString() + }); } else if (gatewayType === 'baileys') { const { whatsappStatus, startWhatsAppBot, isBotRunning } = await import('../services/whatsappBot.mjs'); if (getSetting('whatsapp_enabled', true) && !isBotRunning() && whatsappStatus.connection !== 'open' && whatsappStatus.connection !== 'qr') { diff --git a/services/gowaWhatsappService.js b/services/gowaWhatsappService.js index c585bc6..b9c2b92 100644 --- a/services/gowaWhatsappService.js +++ b/services/gowaWhatsappService.js @@ -44,7 +44,11 @@ async function resolveDeviceId(force) { try { list = await listDevices(); } catch (e) { list = []; } const has = function (id) { return list.some(function (d) { return String(d.id) === id; }); }; let chosen; + const connected = list.find(function (d) { + return d && (d.state === 'connected' || d.is_connected || d.is_logged_in || d.connected); + }); if (configured && has(configured)) chosen = configured; // device id diset & ada -> pakai itu + else if (connected) chosen = String(connected.id); // utamakan device yang benar-benar terhubung else if (list.length === 1) chosen = String(list[0].id); // cuma 1 device -> otomatis else if (list.length > 1) chosen = configured || String(list[0].id); // >1 device -> pakai yg diset / pertama else chosen = configured; // GOWA tak beri list -> serahkan ke GOWA diff --git a/version.txt b/version.txt index a6db491..0d240c6 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.5.16 \ No newline at end of file +0.5.17 \ No newline at end of file diff --git a/views/admin/whatsapp.ejs b/views/admin/whatsapp.ejs index 90d2bc9..8ba0efa 100644 --- a/views/admin/whatsapp.ejs +++ b/views/admin/whatsapp.ejs @@ -290,12 +290,38 @@ // Cooldown QR disinkronkan dengan GoWA: interval mengikuti qr_duration dari server. var _gowaQrRefresh = null; // penjadwal ambil QR berikutnya (timeout) var _gowaQrTick = null; // interval hitung mundur 1 detik + var _gowaQrPoll = null; // pantau status setelah scan function showGowaQr() { var wrap = document.getElementById('gowa_qr_wrap'); if (wrap) wrap.style.display = 'block'; var st = document.getElementById('gowa_qr_status'); - if (st) st.textContent = 'Memuat QR...'; + if (st) { st.style.color = 'var(--muted)'; st.textContent = 'Memuat QR...'; } gowaQrCycle(); + startGowaPoll(); + } + function stopGowaPoll() { + if (_gowaQrPoll) { clearInterval(_gowaQrPoll); _gowaQrPoll = null; } + } + function startGowaPoll() { + stopGowaPoll(); + _gowaQrPoll = setInterval(async function () { + try { + var r = await fetch('/admin/api/whatsapp/status', { cache: 'no-store' }); + var s = await r.json(); + var st = document.getElementById('gowa_qr_status'); + if (s && s.connection === 'open') { + if (_gowaQrRefresh) { clearTimeout(_gowaQrRefresh); _gowaQrRefresh = null; } + if (_gowaQrTick) { clearInterval(_gowaQrTick); _gowaQrTick = null; } + stopGowaPoll(); + var img = document.getElementById('gowa_qr_img'); if (img) img.src = ''; + var wrap = document.getElementById('gowa_qr_wrap'); if (wrap) wrap.style.display = 'none'; + if (st) { st.style.color = 'var(--success)'; st.textContent = '✅ Terhubung ke WhatsApp.'; } + if (typeof checkStatus === 'function') checkStatus(); + } else if (s && s.connection === 'connecting') { + if (st) { st.style.color = 'var(--warning, #f59e0b)'; st.textContent = 'Terautentikasi, sedang menyinkronkan...'; } + } + } catch (e) {} + }, 3000); } function gowaQrCycle() { fetch('/admin/whatsapp/gowa/qr', { cache: 'no-store' }).then(function (r) { return r.json(); }).then(function (d) {