From 66a88faddde8cd03e4200b5bd05307eb79504ec5 Mon Sep 17 00:00:00 2001 From: IMvision12 Date: Fri, 27 Feb 2026 22:07:48 -0800 Subject: [PATCH 1/2] Fix hard reset issue and whatsapp --- .claude/settings.local.json | 17 +++++++ src/cli/commands/auth.ts | 96 ++++++++++++++++++++++++++++--------- src/cli/commands/reset.ts | 70 +++++++++++++++++++-------- src/platforms/whatsapp.ts | 35 +++++++++++++- 4 files changed, 173 insertions(+), 45 deletions(-) create mode 100644 .claude/settings.local.json diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..29794d1 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,17 @@ +{ + "permissions": { + "allow": [ + "WebSearch", + "Bash(xargs grep:*)", + "WebFetch(domain:github.com)", + "WebFetch(domain:docs.openclaw.ai)", + "WebFetch(domain:deepwiki.com)", + "WebFetch(domain:gist.github.com)", + "Bash(gh api:*)", + "WebFetch(domain:raw.githubusercontent.com)", + "Bash(curl:*)", + "Bash(npm run:*)", + "Bash(attrib:*)" + ] + } +} diff --git a/src/cli/commands/auth.ts b/src/cli/commands/auth.ts index 1f7654e..5c566f6 100644 --- a/src/cli/commands/auth.ts +++ b/src/cli/commands/auth.ts @@ -54,9 +54,11 @@ function authenticateWhatsApp(): Promise { rejectPromise = reject; }); - const closeSock = (s: unknown) => { + const closeSock = (s: unknown, removeListeners = false) => { try { - (s as { ws?: { close: () => void } })?.ws?.close(); + const socket = s as { ev?: { removeAllListeners: () => void }; ws?: { close: () => void } }; + if (removeListeners) socket?.ev?.removeAllListeners(); + socket?.ws?.close(); } catch { // Ignore } @@ -78,6 +80,7 @@ function authenticateWhatsApp(): Promise { try { fs.rmSync(WA_AUTH_DIR, { recursive: true, force: true }); + await new Promise((r) => setTimeout(r, 1000)); fs.mkdirSync(WA_AUTH_DIR, { recursive: true }); } catch { console.log(chalk.yellow("Warning: Could not clear old session")); @@ -90,12 +93,33 @@ function authenticateWhatsApp(): Promise { console.log(chalk.gray("Initializing WhatsApp connection...")); console.log(); - const { state, saveCreds } = await useMultiFileAuthState(WA_AUTH_DIR); + const { state, saveCreds: _saveCreds } = await useMultiFileAuthState(WA_AUTH_DIR); + const saveCreds = async () => { + const credsPath = path.join(WA_AUTH_DIR, "creds.json"); + const tmpPath = credsPath + "." + Date.now() + ".tmp"; + for (let attempt = 0; attempt < 10; attempt++) { + try { + fs.writeFileSync(tmpPath, JSON.stringify(state.creds, null, 2)); + try { + fs.unlinkSync(credsPath); + } catch {} + fs.renameSync(tmpPath, credsPath); + return; + } catch { + try { + fs.unlinkSync(tmpPath); + } catch {} + if (attempt < 9) await new Promise((r) => setTimeout(r, 500 * (attempt + 1))); + } + } + // Last resort: try the original saveCreds + await _saveCreds(); + }; const { version } = await fetchLatestBaileysVersion(); connectionTimeout = setTimeout(() => { if (!pairingComplete && sock) { - closeSock(sock); + closeSock(sock, true); if (!connectionAttempted) { rejectPromise( new Error( @@ -142,7 +166,7 @@ function authenticateWhatsApp(): Promise { connectionTimeout = setTimeout(() => { if (!pairingComplete) { - closeSock(sock); + closeSock(sock, true); rejectPromise(new Error("QR code scan timeout - Please try again")); } }, 120000); @@ -154,7 +178,7 @@ function authenticateWhatsApp(): Promise { console.log(chalk.green("\n[OK] WhatsApp authenticated successfully!")); setTimeout(() => { - closeSock(sock); + closeSock(sock, true); resolvePromise(); }, 500); } @@ -166,7 +190,7 @@ function authenticateWhatsApp(): Promise { const errorMessage = lastDisconnect?.error?.message || "Unknown error"; if (!hasShownQR) { - closeSock(sock); + closeSock(sock, true); if (statusCode === 405) { rejectPromise( @@ -189,53 +213,79 @@ function authenticateWhatsApp(): Promise { chalk.cyan("\n[INFO] WhatsApp pairing complete, restarting connection...\n"), ); - closeSock(sock); - - await new Promise((r) => setTimeout(r, 2000)); + // 515 = stream replaced, normal after QR pairing. + // Close websocket but DON'T remove listeners yet (let creds.update finish saving) + closeSock(sock, false); + await new Promise((r) => setTimeout(r, 3000)); - const { state: newState, saveCreds: newSaveCreds } = - await useMultiFileAuthState(WA_AUTH_DIR); + // Now remove old listeners and reconnect using the SAME in-memory state + // (don't re-read from disk — cached keys may not be flushed yet) + closeSock(sock, true); const retryVersion = await fetchLatestBaileysVersion(); const retrySock = makeWASocket({ auth: { - creds: newState.creds, - keys: makeCacheableSignalKeyStore(newState.keys, silentLogger), + creds: state.creds, + keys: makeCacheableSignalKeyStore(state.keys, silentLogger), }, version: retryVersion.version, printQRInTerminal: false, - browser: ["txtcode", "CLI", "0.1.0"], + browser: ["TxtCode", "CLI", "1.0.0"], syncFullHistory: false, markOnlineOnConnect: false, logger: silentLogger, }); - retrySock.ev.on("creds.update", newSaveCreds); + retrySock.ev.on("creds.update", saveCreds); + + const retryTimeout = setTimeout(() => { + if (!pairingComplete) { + closeSock(retrySock, true); + rejectPromise( + new Error("WhatsApp linking timed out after restart. Please try again."), + ); + } + }, 30000); retrySock.ev.on("connection.update", (retryUpdate) => { if (retryUpdate.connection === "open") { + clearTimeout(retryTimeout); pairingComplete = true; console.log(chalk.green("[OK] WhatsApp linked successfully!\n")); setTimeout(() => { - closeSock(retrySock); + closeSock(retrySock, true); resolvePromise(); - }, 500); + }, 1000); } - if (retryUpdate.connection === "close") { - closeSock(retrySock); - rejectPromise(new Error("WhatsApp authentication failed after restart")); + if (retryUpdate.connection === "close" && !pairingComplete) { + const retryStatusCode = ( + retryUpdate as { + lastDisconnect?: { error?: { output?: { statusCode?: number } } }; + } + )?.lastDisconnect?.error?.output?.statusCode; + // If we get another 515, keep retrying + if (retryStatusCode === 515) { + return; + } + clearTimeout(retryTimeout); + closeSock(retrySock, true); + rejectPromise( + new Error( + `WhatsApp authentication failed after restart (code: ${retryStatusCode})`, + ), + ); } }); } else if (hasShownQR && statusCode !== 515) { - closeSock(sock); + closeSock(sock, true); rejectPromise(new Error(`WhatsApp authentication failed (code: ${statusCode})`)); } } }); } catch (error) { if (sock) { - closeSock(sock); + closeSock(sock, true); } rejectPromise(error instanceof Error ? error : new Error(String(error))); } diff --git a/src/cli/commands/reset.ts b/src/cli/commands/reset.ts index 8f13410..c1248c3 100644 --- a/src/cli/commands/reset.ts +++ b/src/cli/commands/reset.ts @@ -1,3 +1,4 @@ +import { execSync } from "child_process"; import fs from "fs"; import os from "os"; import path from "path"; @@ -53,7 +54,7 @@ export async function resetCommand() { export async function logoutCommand() { try { if (fs.existsSync(WA_AUTH_DIR)) { - fs.rmSync(WA_AUTH_DIR, { recursive: true, force: true }); + forceRemove(WA_AUTH_DIR); console.log(); console.log(chalk.green(" ✅ WhatsApp session deleted!")); console.log(chalk.cyan(" Run start to scan QR code again.")); @@ -65,11 +66,36 @@ export async function logoutCommand() { } } catch { console.log(); - console.log(chalk.red(" ❌ Failed to delete session.")); + console.log( + chalk.red( + " ❌ Failed to delete session. Close any running txtcode processes and try again.", + ), + ); console.log(); } } +function forceRemove(target: string): void { + // Try Node's rmSync first + try { + fs.rmSync(target, { recursive: true, force: true, maxRetries: 5, retryDelay: 1000 }); + return; + } catch { + // Fall through to OS-level delete + } + + // Fallback: use OS command which handles locked files better + try { + if (process.platform === "win32") { + execSync(`rmdir /s /q "${target}"`, { stdio: "ignore" }); + } else { + execSync(`rm -rf "${target}"`, { stdio: "ignore" }); + } + } catch { + throw new Error(`Could not delete ${target}`); + } +} + export async function hardResetCommand() { console.log(); console.log(chalk.yellow(" ⚠️ HARD RESET – This will delete ALL TxtCode data:")); @@ -85,31 +111,35 @@ export async function hardResetCommand() { }); if (confirmed) { - let deletedItems = 0; - - try { - if (fs.existsSync(CONFIG_DIR)) { - fs.rmSync(CONFIG_DIR, { recursive: true, force: true }); - console.log(); - console.log(chalk.green(" ✓ Deleted configuration directory")); - deletedItems++; - } - } catch { + if (!fs.existsSync(CONFIG_DIR)) { console.log(); - console.log(chalk.red(" ✗ Failed to delete configuration directory")); + console.log(chalk.yellow(" ⚠️ No data found to delete.")); + console.log(); + return; } - if (deletedItems > 0) { + console.log(); + + try { + forceRemove(CONFIG_DIR); + console.log(chalk.green(" ✓ Deleted all TxtCode data (~/.txtcode)")); console.log(); - console.log(chalk.green(` ✅ Hard reset complete! Deleted ${deletedItems} item(s).`)); + console.log(chalk.green(" ✅ Hard reset complete!")); console.log(); console.log(chalk.cyan(" Run authentication to set up again.")); - console.log(); - } else { - console.log(); - console.log(chalk.yellow(" ⚠️ No data found to delete.")); - console.log(); + } catch { + // Check what's left + if (!fs.existsSync(CONFIG_DIR)) { + console.log(chalk.green(" ✅ Hard reset complete!")); + console.log(); + console.log(chalk.cyan(" Run authentication to set up again.")); + } else { + console.log(chalk.red(" ✗ Failed to delete configuration directory.")); + console.log(chalk.yellow(" Close any running txtcode processes and try again.")); + console.log(chalk.gray(` Or manually delete: ${CONFIG_DIR}`)); + } } + console.log(); } else { console.log(); console.log(chalk.yellow(" ❌ Hard reset cancelled.")); diff --git a/src/platforms/whatsapp.ts b/src/platforms/whatsapp.ts index 98018ee..09e71a7 100644 --- a/src/platforms/whatsapp.ts +++ b/src/platforms/whatsapp.ts @@ -5,6 +5,8 @@ import { Boom } from "@hapi/boom"; import makeWASocket, { type ConnectionState, DisconnectReason, + fetchLatestBaileysVersion, + makeCacheableSignalKeyStore, useMultiFileAuthState, type WASocket, WAMessage, @@ -63,11 +65,39 @@ export class WhatsAppBot { process.exit(1); } - const { state, saveCreds } = await useMultiFileAuthState(WA_AUTH_DIR); + const { state, saveCreds: _saveCreds } = await useMultiFileAuthState(WA_AUTH_DIR); + const saveCreds = async () => { + const credsPath = path.join(WA_AUTH_DIR, "creds.json"); + const tmpPath = credsPath + "." + Date.now() + ".tmp"; + for (let attempt = 0; attempt < 10; attempt++) { + try { + fs.writeFileSync(tmpPath, JSON.stringify(state.creds, null, 2)); + try { + fs.unlinkSync(credsPath); + } catch {} + fs.renameSync(tmpPath, credsPath); + return; + } catch { + try { + fs.unlinkSync(tmpPath); + } catch {} + if (attempt < 9) await new Promise((r) => setTimeout(r, 500 * (attempt + 1))); + } + } + await _saveCreds(); + }; + const { version } = await fetchLatestBaileysVersion().catch(() => ({ + version: undefined as unknown as [number, number, number], + })); this.sock = makeWASocket({ - auth: state, + auth: { + creds: state.creds, + keys: makeCacheableSignalKeyStore(state.keys, silentLogger), + }, + version, printQRInTerminal: false, + browser: ["TxtCode", "CLI", "1.0.0"], logger: silentLogger, }); @@ -77,6 +107,7 @@ export class WhatsAppBot { if (connection === "open") { logger.info("WhatsApp connected!"); logger.info("Waiting for messages..."); + this.sock.sendPresenceUpdate("available").catch(() => {}); } if (connection === "close") { From a884291c25c96cadc4b59799d4e68f261f2c9054 Mon Sep 17 00:00:00 2001 From: IMvision12 Date: Fri, 27 Feb 2026 22:08:16 -0800 Subject: [PATCH 2/2] fix --- .claude/settings.local.json | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 .claude/settings.local.json diff --git a/.claude/settings.local.json b/.claude/settings.local.json deleted file mode 100644 index 29794d1..0000000 --- a/.claude/settings.local.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "permissions": { - "allow": [ - "WebSearch", - "Bash(xargs grep:*)", - "WebFetch(domain:github.com)", - "WebFetch(domain:docs.openclaw.ai)", - "WebFetch(domain:deepwiki.com)", - "WebFetch(domain:gist.github.com)", - "Bash(gh api:*)", - "WebFetch(domain:raw.githubusercontent.com)", - "Bash(curl:*)", - "Bash(npm run:*)", - "Bash(attrib:*)" - ] - } -}