somratpro commited on
Commit
e0ab924
·
1 Parent(s): cebf290

feat: add success cooldown and status check to avoid redundant WhatsApp pairing attempts

Browse files
Files changed (1) hide show
  1. wa-guardian.js +97 -35
wa-guardian.js CHANGED
@@ -9,14 +9,17 @@
9
 
10
  const fs = require("fs");
11
  const path = require("path");
12
- const { WebSocket } = require('/home/node/.openclaw/openclaw-app/node_modules/ws');
13
- const { randomUUID } = require('node:crypto');
 
 
14
 
15
  const GATEWAY_URL = "ws://127.0.0.1:7860";
16
  const GATEWAY_TOKEN = process.env.GATEWAY_TOKEN || "huggingclaw";
17
  const CHECK_INTERVAL = 5000;
18
  const WAIT_TIMEOUT = 120000;
19
  const POST_515_NO_LOGOUT_MS = 90 * 1000;
 
20
  const RESET_MARKER_PATH = path.join(
21
  process.env.HOME || "/home/node",
22
  ".openclaw",
@@ -27,11 +30,13 @@ const RESET_MARKER_PATH = path.join(
27
  let isWaiting = false;
28
  let hasShownWaitMessage = false;
29
  let last515At = 0;
 
30
 
31
  function extractErrorMessage(msg) {
32
  if (!msg || typeof msg !== "object") return "Unknown error";
33
  if (typeof msg.error === "string") return msg.error;
34
- if (msg.error && typeof msg.error.message === "string") return msg.error.message;
 
35
  if (typeof msg.message === "string") return msg.message;
36
  return "Unknown error";
37
  }
@@ -40,9 +45,13 @@ function writeResetMarker() {
40
  try {
41
  fs.mkdirSync(path.dirname(RESET_MARKER_PATH), { recursive: true });
42
  fs.writeFileSync(RESET_MARKER_PATH, "reset\n");
43
- console.log(`[guardian] Created backup reset marker at ${RESET_MARKER_PATH}`);
 
 
44
  } catch (error) {
45
- console.log(`[guardian] Failed to write backup reset marker: ${error.message}`);
 
 
46
  }
47
  }
48
 
@@ -55,25 +64,32 @@ async function createConnection() {
55
  const msg = JSON.parse(data.toString());
56
 
57
  if (msg.type === "event" && msg.event === "connect.challenge") {
58
- ws.send(JSON.stringify({
59
- type: "req",
60
- id: randomUUID(),
61
- method: "connect",
62
- params: {
63
- minProtocol: 3,
64
- maxProtocol: 3,
65
- client: {
66
- id: "gateway-client",
67
- version: "1.0.0",
68
- platform: "linux",
69
- mode: "backend",
 
 
 
 
 
 
 
 
 
 
 
70
  },
71
- caps: [],
72
- auth: { token: GATEWAY_TOKEN },
73
- role: "operator",
74
- scopes: ["operator.read", "operator.write", "operator.admin", "operator.pairing"],
75
- },
76
- }));
77
  return;
78
  }
79
 
@@ -90,8 +106,15 @@ async function createConnection() {
90
  }
91
  });
92
 
93
- ws.on("error", (e) => { if (!resolved) reject(e); });
94
- setTimeout(() => { if (!resolved) { ws.close(); reject(new Error("Timeout")); } }, 10000);
 
 
 
 
 
 
 
95
  });
96
  }
97
 
@@ -111,24 +134,49 @@ async function callRpc(ws, method, params) {
111
  };
112
  ws.on("message", handler);
113
  ws.send(JSON.stringify({ type: "req", id, method, params }));
114
- setTimeout(() => { ws.removeListener("message", handler); reject(new Error("RPC Timeout")); }, WAIT_TIMEOUT + 5000);
 
 
 
115
  });
116
  }
117
 
118
  async function checkStatus() {
119
  if (isWaiting) return;
 
 
120
 
121
  let ws;
122
  try {
123
  ws = await createConnection();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
  isWaiting = true;
125
  if (!hasShownWaitMessage) {
126
- console.log("\n[guardian] 📱 WhatsApp pairing in progress. Please scan the QR code in the Control UI.");
 
 
127
  hasShownWaitMessage = true;
128
  }
129
 
130
  console.log("[guardian] Waiting for pairing completion...");
131
- const waitRes = await callRpc(ws, "web.login.wait", { timeoutMs: WAIT_TIMEOUT });
 
 
132
  const result = waitRes.payload || waitRes.result;
133
  const message = result?.message || "";
134
  const linkedAfter515 = !result?.connected && message.includes("515");
@@ -139,29 +187,39 @@ async function checkStatus() {
139
 
140
  if (result && (result.connected || linkedAfter515)) {
141
  hasShownWaitMessage = false;
 
142
 
143
  if (linkedAfter515) {
144
- console.log("[guardian] 515 after scan: credentials saved, reloading config to start WhatsApp...");
 
 
145
  } else {
146
  console.log("[guardian] ✅ Pairing completed! Reloading config...");
147
  }
148
 
149
  const getRes = await callRpc(ws, "config.get", {});
150
  if (getRes.payload?.raw && getRes.payload?.hash) {
151
- await callRpc(ws, "config.apply", { raw: getRes.payload.raw, baseHash: getRes.payload.hash });
 
 
 
152
  console.log("[guardian] Configuration re-applied.");
153
  }
154
- } else if (!message.includes("No active") && !message.includes("Still waiting")) {
 
 
 
155
  console.log(`[guardian] Wait result: ${message}`);
156
  }
157
-
158
  } catch (e) {
159
  const message = e && e.message ? e.message : "";
160
  if (
161
  /401|unauthorized|logged out|440|conflict/i.test(message) &&
162
  Date.now() - last515At >= POST_515_NO_LOGOUT_MS
163
  ) {
164
- console.log("[guardian] Clearing invalid WhatsApp session so a fresh QR can be used...");
 
 
165
  try {
166
  if (ws) {
167
  await callRpc(ws, "channels.logout", { channel: "whatsapp" });
@@ -170,7 +228,9 @@ async function checkStatus() {
170
  console.log("[guardian] Logged out invalid WhatsApp session.");
171
  }
172
  } catch (error) {
173
- console.log(`[guardian] Failed to log out invalid session: ${error.message}`);
 
 
174
  }
175
  }
176
  // Normal timeout or gateway starting up; retry on the next interval.
@@ -180,6 +240,8 @@ async function checkStatus() {
180
  }
181
  }
182
 
183
- console.log("[guardian] ⚔️ WhatsApp Guardian active. Monitoring pairing status...");
 
 
184
  setInterval(checkStatus, CHECK_INTERVAL);
185
  setTimeout(checkStatus, 15000);
 
9
 
10
  const fs = require("fs");
11
  const path = require("path");
12
+ const {
13
+ WebSocket,
14
+ } = require("/home/node/.openclaw/openclaw-app/node_modules/ws");
15
+ const { randomUUID } = require("node:crypto");
16
 
17
  const GATEWAY_URL = "ws://127.0.0.1:7860";
18
  const GATEWAY_TOKEN = process.env.GATEWAY_TOKEN || "huggingclaw";
19
  const CHECK_INTERVAL = 5000;
20
  const WAIT_TIMEOUT = 120000;
21
  const POST_515_NO_LOGOUT_MS = 90 * 1000;
22
+ const SUCCESS_COOLDOWN_MS = 60 * 1000;
23
  const RESET_MARKER_PATH = path.join(
24
  process.env.HOME || "/home/node",
25
  ".openclaw",
 
30
  let isWaiting = false;
31
  let hasShownWaitMessage = false;
32
  let last515At = 0;
33
+ let lastConnectedAt = 0;
34
 
35
  function extractErrorMessage(msg) {
36
  if (!msg || typeof msg !== "object") return "Unknown error";
37
  if (typeof msg.error === "string") return msg.error;
38
+ if (msg.error && typeof msg.error.message === "string")
39
+ return msg.error.message;
40
  if (typeof msg.message === "string") return msg.message;
41
  return "Unknown error";
42
  }
 
45
  try {
46
  fs.mkdirSync(path.dirname(RESET_MARKER_PATH), { recursive: true });
47
  fs.writeFileSync(RESET_MARKER_PATH, "reset\n");
48
+ console.log(
49
+ `[guardian] Created backup reset marker at ${RESET_MARKER_PATH}`,
50
+ );
51
  } catch (error) {
52
+ console.log(
53
+ `[guardian] Failed to write backup reset marker: ${error.message}`,
54
+ );
55
  }
56
  }
57
 
 
64
  const msg = JSON.parse(data.toString());
65
 
66
  if (msg.type === "event" && msg.event === "connect.challenge") {
67
+ ws.send(
68
+ JSON.stringify({
69
+ type: "req",
70
+ id: randomUUID(),
71
+ method: "connect",
72
+ params: {
73
+ minProtocol: 3,
74
+ maxProtocol: 3,
75
+ client: {
76
+ id: "gateway-client",
77
+ version: "1.0.0",
78
+ platform: "linux",
79
+ mode: "backend",
80
+ },
81
+ caps: [],
82
+ auth: { token: GATEWAY_TOKEN },
83
+ role: "operator",
84
+ scopes: [
85
+ "operator.read",
86
+ "operator.write",
87
+ "operator.admin",
88
+ "operator.pairing",
89
+ ],
90
  },
91
+ }),
92
+ );
 
 
 
 
93
  return;
94
  }
95
 
 
106
  }
107
  });
108
 
109
+ ws.on("error", (e) => {
110
+ if (!resolved) reject(e);
111
+ });
112
+ setTimeout(() => {
113
+ if (!resolved) {
114
+ ws.close();
115
+ reject(new Error("Timeout"));
116
+ }
117
+ }, 10000);
118
  });
119
  }
120
 
 
134
  };
135
  ws.on("message", handler);
136
  ws.send(JSON.stringify({ type: "req", id, method, params }));
137
+ setTimeout(() => {
138
+ ws.removeListener("message", handler);
139
+ reject(new Error("RPC Timeout"));
140
+ }, WAIT_TIMEOUT + 5000);
141
  });
142
  }
143
 
144
  async function checkStatus() {
145
  if (isWaiting) return;
146
+ if (lastConnectedAt && Date.now() - lastConnectedAt < SUCCESS_COOLDOWN_MS)
147
+ return;
148
 
149
  let ws;
150
  try {
151
  ws = await createConnection();
152
+
153
+ const statusRes = await callRpc(ws, "channels.status", {});
154
+ const channels = (statusRes.payload || statusRes.result)?.channels || {};
155
+ const wa = channels.whatsapp;
156
+
157
+ if (!wa) {
158
+ hasShownWaitMessage = false;
159
+ return;
160
+ }
161
+
162
+ if (wa.connected) {
163
+ hasShownWaitMessage = false;
164
+ lastConnectedAt = Date.now();
165
+ return;
166
+ }
167
+
168
  isWaiting = true;
169
  if (!hasShownWaitMessage) {
170
+ console.log(
171
+ "\n[guardian] 📱 WhatsApp pairing in progress. Please scan the QR code in the Control UI.",
172
+ );
173
  hasShownWaitMessage = true;
174
  }
175
 
176
  console.log("[guardian] Waiting for pairing completion...");
177
+ const waitRes = await callRpc(ws, "web.login.wait", {
178
+ timeoutMs: WAIT_TIMEOUT,
179
+ });
180
  const result = waitRes.payload || waitRes.result;
181
  const message = result?.message || "";
182
  const linkedAfter515 = !result?.connected && message.includes("515");
 
187
 
188
  if (result && (result.connected || linkedAfter515)) {
189
  hasShownWaitMessage = false;
190
+ lastConnectedAt = Date.now();
191
 
192
  if (linkedAfter515) {
193
+ console.log(
194
+ "[guardian] 515 after scan: credentials saved, reloading config to start WhatsApp...",
195
+ );
196
  } else {
197
  console.log("[guardian] ✅ Pairing completed! Reloading config...");
198
  }
199
 
200
  const getRes = await callRpc(ws, "config.get", {});
201
  if (getRes.payload?.raw && getRes.payload?.hash) {
202
+ await callRpc(ws, "config.apply", {
203
+ raw: getRes.payload.raw,
204
+ baseHash: getRes.payload.hash,
205
+ });
206
  console.log("[guardian] Configuration re-applied.");
207
  }
208
+ } else if (
209
+ !message.includes("No active") &&
210
+ !message.includes("Still waiting")
211
+ ) {
212
  console.log(`[guardian] Wait result: ${message}`);
213
  }
 
214
  } catch (e) {
215
  const message = e && e.message ? e.message : "";
216
  if (
217
  /401|unauthorized|logged out|440|conflict/i.test(message) &&
218
  Date.now() - last515At >= POST_515_NO_LOGOUT_MS
219
  ) {
220
+ console.log(
221
+ "[guardian] Clearing invalid WhatsApp session so a fresh QR can be used...",
222
+ );
223
  try {
224
  if (ws) {
225
  await callRpc(ws, "channels.logout", { channel: "whatsapp" });
 
228
  console.log("[guardian] Logged out invalid WhatsApp session.");
229
  }
230
  } catch (error) {
231
+ console.log(
232
+ `[guardian] Failed to log out invalid session: ${error.message}`,
233
+ );
234
  }
235
  }
236
  // Normal timeout or gateway starting up; retry on the next interval.
 
240
  }
241
  }
242
 
243
+ console.log(
244
+ "[guardian] ⚔️ WhatsApp Guardian active. Monitoring pairing status...",
245
+ );
246
  setInterval(checkStatus, CHECK_INTERVAL);
247
  setTimeout(checkStatus, 15000);