feat: update openclaw and polish desktop flows
This commit is contained in:
@@ -5,6 +5,12 @@ import { EventEmitter } from 'events';
|
||||
import { existsSync, mkdirSync, rmSync, readdirSync } from 'fs';
|
||||
import { deflateSync } from 'zlib';
|
||||
import { getOpenClawDir, getOpenClawResolvedDir } from './paths';
|
||||
import { getAllSettings } from './store';
|
||||
import {
|
||||
isSocksProxyUrl,
|
||||
redactProxyUrlForLog,
|
||||
resolveWhatsAppProxyUrl,
|
||||
} from './whatsapp-proxy';
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
@@ -76,6 +82,32 @@ type ConnectionState = {
|
||||
const QRCode = QRCodeModule;
|
||||
const QRErrorCorrectLevel = QRErrorCorrectLevelModule;
|
||||
|
||||
function loadProxyAgentConstructor(proxyUrl: string) {
|
||||
const moduleName = isSocksProxyUrl(proxyUrl) ? 'socks-proxy-agent' : 'https-proxy-agent';
|
||||
const agentModule = require(resolveOpenClawModule(moduleName));
|
||||
return agentModule.SocksProxyAgent
|
||||
|| agentModule.HttpsProxyAgent
|
||||
|| agentModule.default
|
||||
|| agentModule;
|
||||
}
|
||||
|
||||
async function createWhatsAppSocketAgent(): Promise<unknown | undefined> {
|
||||
let settings: Awaited<ReturnType<typeof getAllSettings>>;
|
||||
try {
|
||||
settings = await getAllSettings();
|
||||
} catch (error) {
|
||||
console.warn('[WhatsAppLogin] Failed to read proxy settings; connecting directly', error);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const proxyUrl = resolveWhatsAppProxyUrl(settings);
|
||||
if (!proxyUrl) return undefined;
|
||||
|
||||
const AgentCtor = loadProxyAgentConstructor(proxyUrl);
|
||||
console.log(`[WhatsAppLogin] Using proxy for WhatsApp WebSocket: ${redactProxyUrlForLog(proxyUrl)}`);
|
||||
return new AgentCtor(proxyUrl);
|
||||
}
|
||||
|
||||
function createQrMatrix(input: string) {
|
||||
const qr = new QRCode(-1, QRErrorCorrectLevel.L);
|
||||
qr.addData(input);
|
||||
@@ -201,6 +233,7 @@ export class WhatsAppLoginManager extends EventEmitter {
|
||||
private loginSucceeded: boolean = false;
|
||||
private retryCount: number = 0;
|
||||
private maxRetries: number = 5;
|
||||
private lastConnectionError: string | null = null;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@@ -242,10 +275,16 @@ export class WhatsAppLoginManager extends EventEmitter {
|
||||
this.loginSucceeded = false;
|
||||
this.qr = null;
|
||||
this.retryCount = 0;
|
||||
this.lastConnectionError = null;
|
||||
|
||||
await this.connectToWhatsApp(accountId);
|
||||
}
|
||||
|
||||
private buildConnectionFailureMessage(fallback: string): string {
|
||||
const details = this.lastConnectionError ? ` Last error: ${this.lastConnectionError}` : '';
|
||||
return `${fallback}. Check network connectivity or enable a proxy in Advanced Settings.${details}`;
|
||||
}
|
||||
|
||||
private async connectToWhatsApp(accountId: string): Promise<void> {
|
||||
if (!this.active) return;
|
||||
|
||||
@@ -292,6 +331,7 @@ export class WhatsAppLoginManager extends EventEmitter {
|
||||
const { version } = await fetchLatestBaileysVersion();
|
||||
|
||||
console.log(`[WhatsAppLogin] Starting login for ${accountId}, version: ${version}`);
|
||||
const socketAgent = await createWhatsAppSocketAgent();
|
||||
|
||||
this.socket = makeWASocket({
|
||||
version,
|
||||
@@ -299,6 +339,7 @@ export class WhatsAppLoginManager extends EventEmitter {
|
||||
printQRInTerminal: false,
|
||||
logger: pino({ level: 'silent' }), // Silent logger
|
||||
connectTimeoutMs: 60000,
|
||||
...(socketAgent ? { agent: socketAgent } : {}),
|
||||
// mobile: false,
|
||||
// browser: ['ClawX', 'Chrome', '1.0.0'],
|
||||
});
|
||||
@@ -333,6 +374,8 @@ export class WhatsAppLoginManager extends EventEmitter {
|
||||
if (connection === 'close') {
|
||||
const error = lastDisconnect?.error as BaileysError | undefined;
|
||||
const statusCode = error?.output?.statusCode;
|
||||
this.lastConnectionError = error?.message
|
||||
|| (statusCode ? `statusCode=${statusCode}` : this.lastConnectionError);
|
||||
const isLoggedOut = statusCode === DisconnectReason.loggedOut;
|
||||
// Treat 401 as transient if we haven't exhausted retries (max 2 attempts)
|
||||
// This handles the case where WhatsApp's session hasn't fully released
|
||||
@@ -351,7 +394,7 @@ export class WhatsAppLoginManager extends EventEmitter {
|
||||
} else {
|
||||
console.log('[WhatsAppLogin] Max retries reached, stopping.');
|
||||
this.active = false;
|
||||
this.emit('error', 'Connection failed after multiple retries');
|
||||
this.emit('error', this.buildConnectionFailureMessage('Connection failed after multiple retries'));
|
||||
}
|
||||
} else {
|
||||
// Logged out or explicitly stopped
|
||||
@@ -384,6 +427,12 @@ export class WhatsAppLoginManager extends EventEmitter {
|
||||
}
|
||||
} catch (innerErr) {
|
||||
console.error('[WhatsAppLogin] Error in connection update:', innerErr);
|
||||
const msg = innerErr instanceof Error ? innerErr.message : String(innerErr);
|
||||
this.lastConnectionError = msg;
|
||||
if (this.active) {
|
||||
this.active = false;
|
||||
this.emit('error', this.buildConnectionFailureMessage('Failed to generate WhatsApp QR code'));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -395,7 +444,8 @@ export class WhatsAppLoginManager extends EventEmitter {
|
||||
} else {
|
||||
this.active = false;
|
||||
const msg = error instanceof Error ? error.message : String(error);
|
||||
this.emit('error', msg);
|
||||
this.lastConnectionError = msg;
|
||||
this.emit('error', this.buildConnectionFailureMessage('Failed to start WhatsApp login'));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -408,6 +458,7 @@ export class WhatsAppLoginManager extends EventEmitter {
|
||||
const cleanupAccountId = this.accountId;
|
||||
this.active = false;
|
||||
this.qr = null;
|
||||
this.lastConnectionError = null;
|
||||
if (this.socket) {
|
||||
try {
|
||||
// Remove listeners to prevent handling closure as error
|
||||
|
||||
59
electron/utils/whatsapp-proxy.ts
Normal file
59
electron/utils/whatsapp-proxy.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import type { ProxySettings } from './proxy';
|
||||
import { resolveProxySettings } from './proxy';
|
||||
|
||||
const WHATSAPP_WEB_HOST = 'web.whatsapp.com';
|
||||
|
||||
function splitBypassRules(rules: string): string[] {
|
||||
return rules
|
||||
.split(/[,\n;]/)
|
||||
.map((rule) => rule.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function matchesBypassRule(host: string, rule: string): boolean {
|
||||
const normalizedHost = host.toLowerCase();
|
||||
const normalizedRule = rule.toLowerCase();
|
||||
if (normalizedRule === '*') return true;
|
||||
if (normalizedRule === '<local>') {
|
||||
return normalizedHost === 'localhost'
|
||||
|| normalizedHost === '127.0.0.1'
|
||||
|| normalizedHost === '::1'
|
||||
|| !normalizedHost.includes('.');
|
||||
}
|
||||
if (normalizedRule.startsWith('*.')) {
|
||||
const suffix = normalizedRule.slice(1);
|
||||
return normalizedHost.endsWith(suffix);
|
||||
}
|
||||
if (normalizedRule.startsWith('.')) {
|
||||
return normalizedHost.endsWith(normalizedRule);
|
||||
}
|
||||
return normalizedHost === normalizedRule;
|
||||
}
|
||||
|
||||
export function shouldBypassWhatsAppProxy(bypassRules: string): boolean {
|
||||
return splitBypassRules(bypassRules).some((rule) => matchesBypassRule(WHATSAPP_WEB_HOST, rule));
|
||||
}
|
||||
|
||||
export function resolveWhatsAppProxyUrl(settings: ProxySettings): string {
|
||||
if (!settings.proxyEnabled) return '';
|
||||
|
||||
const resolved = resolveProxySettings(settings);
|
||||
if (shouldBypassWhatsAppProxy(resolved.bypassRules)) return '';
|
||||
|
||||
return resolved.allProxy || resolved.httpsProxy || resolved.httpProxy;
|
||||
}
|
||||
|
||||
export function isSocksProxyUrl(proxyUrl: string): boolean {
|
||||
return /^socks[45]h?:\/\//i.test(proxyUrl.trim());
|
||||
}
|
||||
|
||||
export function redactProxyUrlForLog(proxyUrl: string): string {
|
||||
try {
|
||||
const parsed = new URL(proxyUrl);
|
||||
if (parsed.username) parsed.username = 'redacted';
|
||||
if (parsed.password) parsed.password = 'redacted';
|
||||
return parsed.toString();
|
||||
} catch {
|
||||
return proxyUrl.replace(/\/\/([^/@:]+):([^/@]+)@/, '//redacted:redacted@');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user