| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| "use strict"; |
|
|
| const { WebSocket } = require("ws"); |
| const { randomUUID } = require("node:crypto"); |
| const { exec } = require('child_process'); |
|
|
| const GATEWAY_URL = "ws://127.0.0.1:7860"; |
| const TOKEN = "openclaw-space-default"; |
| const CHECK_INTERVAL = 5000; |
| const WAIT_TIMEOUT = 120000; |
| const POST_515_NO_LOGOUT_MS = 90000; |
|
|
| let isWaiting = false; |
| let last515At = 0; |
| let hasShownWaitMessage = false; |
|
|
| function createConnection() { |
| return new Promise((resolve, reject) => { |
| const ws = new WebSocket(GATEWAY_URL); |
| let resolved = false; |
|
|
| ws.on("message", (data) => { |
| const msg = JSON.parse(data.toString()); |
|
|
| if (msg.type === "event" && msg.event === "connect.challenge") { |
| ws.send( |
| JSON.stringify({ |
| type: "req", |
| id: randomUUID(), |
| method: "connect", |
| params: { |
| minProtocol: 3, |
| maxProtocol: 3, |
| client: { |
| id: "gateway-client", |
| version: "1.0.0", |
| platform: "linux", |
| mode: "backend", |
| }, |
| caps: [], |
| auth: { token: TOKEN }, |
| role: "operator", |
| scopes: ["operator.admin"], |
| }, |
| }) |
| ); |
| return; |
| } |
|
|
| if (!resolved && msg.type === "res" && msg.ok) { |
| resolved = true; |
| resolve(ws); |
| } |
| }); |
|
|
| ws.on("error", (e) => { |
| if (!resolved) reject(e); |
| }); |
|
|
| setTimeout(() => { |
| if (!resolved) { |
| ws.close(); |
| reject(new Error("Connection timeout")); |
| } |
| }, 10000); |
| }); |
| } |
|
|
| async function callRpc(ws, method, params) { |
| return new Promise((resolve, reject) => { |
| const id = randomUUID(); |
| const handler = (data) => { |
| const msg = JSON.parse(data.toString()); |
| if (msg.id === id) { |
| ws.removeListener("message", handler); |
| resolve(msg); |
| } |
| }; |
| ws.on("message", handler); |
| ws.send(JSON.stringify({ type: "req", id, method, params })); |
|
|
| |
| setTimeout(() => { |
| ws.removeListener("message", handler); |
| reject(new Error("RPC timeout")); |
| }, WAIT_TIMEOUT + 5000); |
| }); |
| } |
|
|
| async function checkAndWait() { |
| if (isWaiting) return; |
|
|
| let ws; |
| try { |
| ws = await createConnection(); |
| } catch { |
| return; |
| } |
|
|
| try { |
| |
| const statusRes = await callRpc(ws, "channels.status", {}); |
| const channels = (statusRes.payload || statusRes.result)?.channels || {}; |
| const wa = channels.whatsapp; |
|
|
| if (!wa) { |
| ws.close(); |
| return; |
| } |
|
|
| |
| |
| const err = (wa.lastError || "").toLowerCase(); |
| const recently515 = Date.now() - last515At < POST_515_NO_LOGOUT_MS; |
| const needsLogout = wa.linked && !wa.connected && !recently515 && |
| (err.includes("401") || err.includes("unauthorized") || err.includes("logged out") || err.includes("440") || err.includes("conflict")); |
|
|
| if (needsLogout) { |
| console.log("[wa-guardian] Clearing invalid session (401/440/conflict) so a fresh QR can be used..."); |
| try { |
| await callRpc(ws, "channels.logout", { channel: "whatsapp" }); |
| console.log("[wa-guardian] Logged out; user can click Login again for a new QR."); |
| |
| |
| const fs = require('fs'); |
| const path = require('path'); |
| |
| const markerPath = path.join(process.env.HOME || '/home/node', '.openclaw/workspace/.reset_credentials'); |
| fs.writeFileSync(markerPath, 'reset'); |
| console.log("[wa-guardian] Created .reset_credentials marker for sync script."); |
| |
| } catch (e) { |
| console.log("[wa-guardian] channels.logout failed:", e.message); |
| } |
| ws.close(); |
| return; |
| } |
|
|
| |
| if (wa.connected) { |
| ws.close(); |
| return; |
| } |
|
|
| |
| isWaiting = true; |
| if (!hasShownWaitMessage) { |
| console.log("β³ Waiting for WhatsApp QR code scan..."); |
| console.log("π± Please scan the QR code with your phone to continue."); |
| hasShownWaitMessage = true; |
| } |
| console.log("[wa-guardian] Calling web.login.wait..."); |
| const waitRes = await callRpc(ws, "web.login.wait", { |
| timeoutMs: WAIT_TIMEOUT, |
| }); |
| const result = waitRes.payload || waitRes.result; |
| const msg = result?.message || ""; |
| const linkedAfter515 = !result?.connected && msg.includes("515"); |
| if (linkedAfter515) last515At = Date.now(); |
| if (result?.connected || linkedAfter515) { |
| hasShownWaitMessage = false; |
| if (linkedAfter515) { |
| console.log("[wa-guardian] 515 after scan β credentials saved; triggering config reload to start channel..."); |
| } else { |
| console.log("[wa-guardian] WhatsApp connected successfully! Triggering config reload to start channel..."); |
| } |
| console.log("β
QR code scanned successfully. Login completed."); |
|
|
| |
| try { |
| const getRes = await callRpc(ws, "config.get", {}); |
| const raw = getRes.payload?.raw; |
| const hash = getRes.payload?.hash; |
| if (raw && hash) { |
| await callRpc(ws, "config.apply", { raw, baseHash: hash }); |
| console.log("[wa-guardian] Config applied; gateway will restart with WhatsApp channel."); |
| } |
| } catch (e) { |
| console.log("[wa-guardian] Config apply failed:", e.message); |
| } |
| } else { |
| if (!msg.includes("No active") && !msg.includes("Still waiting")) { |
| console.log("[wa-guardian] Wait result:", msg); |
| } |
| } |
| } catch (e) { |
| |
| } finally { |
| isWaiting = false; |
| try { |
| ws.close(); |
| } catch {} |
| } |
| } |
|
|
| |
| console.log("[wa-guardian] WhatsApp login guardian started"); |
| setInterval(checkAndWait, CHECK_INTERVAL); |
| |
| setTimeout(checkAndWait, 15000); |
|
|