refactor: centralize whatsapp auth detection
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
|
||||
@@ -10,12 +9,10 @@ import {
|
||||
readConfigFileSnapshot,
|
||||
writeConfigFile,
|
||||
} from "../config/config.js";
|
||||
import { resolveOAuthDir } from "../config/paths.js";
|
||||
import { DEFAULT_ACCOUNT_ID } from "../routing/session-key.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { note } from "../terminal/note.js";
|
||||
import { resolveUserPath } from "../utils.js";
|
||||
import { resolveWebCredsPath } from "../web/auth-store.js";
|
||||
import { hasAnyWhatsAppAuth } from "../web/accounts.js";
|
||||
|
||||
function resolveLegacyConfigPath(env: NodeJS.ProcessEnv): string {
|
||||
const override = env.CLAWDIS_CONFIG_PATH?.trim();
|
||||
@@ -57,48 +54,6 @@ export function replaceModernName(value: string | undefined): string | undefined
|
||||
return value.replace(/clawdbot/g, "clawdis");
|
||||
}
|
||||
|
||||
function hasWebCreds(authDir: string): boolean {
|
||||
try {
|
||||
const credsPath = resolveWebCredsPath(authDir);
|
||||
const stats = fs.statSync(credsPath);
|
||||
return stats.isFile() && stats.size > 1;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function listWhatsAppAuthDirs(cfg: ClawdbotConfig): string[] {
|
||||
const oauthDir = resolveOAuthDir();
|
||||
const whatsappDir = path.join(oauthDir, "whatsapp");
|
||||
const authDirs = new Set<string>([oauthDir, path.join(whatsappDir, DEFAULT_ACCOUNT_ID)]);
|
||||
|
||||
const accounts = cfg.channels?.whatsapp?.accounts;
|
||||
if (accounts && typeof accounts === "object") {
|
||||
for (const [accountId, accountCfg] of Object.entries(accounts)) {
|
||||
const configured = accountCfg?.authDir?.trim();
|
||||
const authDir = configured ? resolveUserPath(configured) : path.join(whatsappDir, accountId);
|
||||
authDirs.add(authDir);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const entries = fs.readdirSync(whatsappDir, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
if (!entry.isDirectory()) continue;
|
||||
authDirs.add(path.join(whatsappDir, entry.name));
|
||||
}
|
||||
} catch {
|
||||
// ignore missing dirs
|
||||
}
|
||||
|
||||
return Array.from(authDirs);
|
||||
}
|
||||
|
||||
function hasWhatsAppAuthState(cfg: ClawdbotConfig): boolean {
|
||||
const authDirs = listWhatsAppAuthDirs(cfg);
|
||||
return authDirs.some((authDir) => hasWebCreds(authDir));
|
||||
}
|
||||
|
||||
export function normalizeLegacyConfigValues(cfg: ClawdbotConfig): {
|
||||
config: ClawdbotConfig;
|
||||
changes: string[];
|
||||
@@ -266,7 +221,7 @@ export function normalizeLegacyConfigValues(cfg: ClawdbotConfig): {
|
||||
|
||||
const legacyAckReaction = cfg.messages?.ackReaction?.trim();
|
||||
const hasWhatsAppConfig = cfg.channels?.whatsapp !== undefined;
|
||||
const hasWhatsAppAuth = hasWhatsAppAuthState(cfg);
|
||||
const hasWhatsAppAuth = hasAnyWhatsAppAuth(cfg);
|
||||
if (legacyAckReaction && (hasWhatsAppConfig || hasWhatsAppAuth)) {
|
||||
const hasWhatsAppAck = cfg.channels?.whatsapp?.ackReaction !== undefined;
|
||||
if (!hasWhatsAppAck) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { resolveOAuthDir } from "../config/paths.js";
|
||||
import type { DmPolicy, GroupPolicy, WhatsAppAccountConfig } from "../config/types.js";
|
||||
import { DEFAULT_ACCOUNT_ID } from "../routing/session-key.js";
|
||||
import { resolveUserPath } from "../utils.js";
|
||||
import { hasWebCredsSync } from "./auth-store.js";
|
||||
|
||||
export type ResolvedWhatsAppAccount = {
|
||||
accountId: string;
|
||||
@@ -32,6 +33,33 @@ function listConfiguredAccountIds(cfg: ClawdbotConfig): string[] {
|
||||
return Object.keys(accounts).filter(Boolean);
|
||||
}
|
||||
|
||||
export function listWhatsAppAuthDirs(cfg: ClawdbotConfig): string[] {
|
||||
const oauthDir = resolveOAuthDir();
|
||||
const whatsappDir = path.join(oauthDir, "whatsapp");
|
||||
const authDirs = new Set<string>([oauthDir, path.join(whatsappDir, DEFAULT_ACCOUNT_ID)]);
|
||||
|
||||
const accountIds = listConfiguredAccountIds(cfg);
|
||||
for (const accountId of accountIds) {
|
||||
authDirs.add(resolveWhatsAppAuthDir({ cfg, accountId }).authDir);
|
||||
}
|
||||
|
||||
try {
|
||||
const entries = fs.readdirSync(whatsappDir, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
if (!entry.isDirectory()) continue;
|
||||
authDirs.add(path.join(whatsappDir, entry.name));
|
||||
}
|
||||
} catch {
|
||||
// ignore missing dirs
|
||||
}
|
||||
|
||||
return Array.from(authDirs);
|
||||
}
|
||||
|
||||
export function hasAnyWhatsAppAuth(cfg: ClawdbotConfig): boolean {
|
||||
return listWhatsAppAuthDirs(cfg).some((authDir) => hasWebCredsSync(authDir));
|
||||
}
|
||||
|
||||
export function listWhatsAppAccountIds(cfg: ClawdbotConfig): string[] {
|
||||
const ids = listConfiguredAccountIds(cfg);
|
||||
if (ids.length === 0) return [DEFAULT_ACCOUNT_ID];
|
||||
|
||||
63
src/web/accounts.whatsapp-auth.test.ts
Normal file
63
src/web/accounts.whatsapp-auth.test.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
|
||||
import { hasAnyWhatsAppAuth, listWhatsAppAuthDirs } from "./accounts.js";
|
||||
|
||||
describe("hasAnyWhatsAppAuth", () => {
|
||||
let previousOauthDir: string | undefined;
|
||||
let tempOauthDir: string | undefined;
|
||||
|
||||
const writeCreds = (dir: string) => {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
fs.writeFileSync(path.join(dir, "creds.json"), JSON.stringify({ me: {} }));
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
previousOauthDir = process.env.CLAWDBOT_OAUTH_DIR;
|
||||
tempOauthDir = fs.mkdtempSync(path.join(os.tmpdir(), "clawdbot-oauth-"));
|
||||
process.env.CLAWDBOT_OAUTH_DIR = tempOauthDir;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (previousOauthDir === undefined) {
|
||||
delete process.env.CLAWDBOT_OAUTH_DIR;
|
||||
} else {
|
||||
process.env.CLAWDBOT_OAUTH_DIR = previousOauthDir;
|
||||
}
|
||||
if (tempOauthDir) {
|
||||
fs.rmSync(tempOauthDir, { recursive: true, force: true });
|
||||
tempOauthDir = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
it("returns false when no auth exists", () => {
|
||||
expect(hasAnyWhatsAppAuth({})).toBe(false);
|
||||
});
|
||||
|
||||
it("returns true when legacy auth exists", () => {
|
||||
fs.writeFileSync(path.join(tempOauthDir ?? "", "creds.json"), JSON.stringify({ me: {} }));
|
||||
expect(hasAnyWhatsAppAuth({})).toBe(true);
|
||||
});
|
||||
|
||||
it("returns true when non-default auth exists", () => {
|
||||
writeCreds(path.join(tempOauthDir ?? "", "whatsapp", "work"));
|
||||
expect(hasAnyWhatsAppAuth({})).toBe(true);
|
||||
});
|
||||
|
||||
it("includes authDir overrides", () => {
|
||||
const customDir = fs.mkdtempSync(path.join(os.tmpdir(), "clawdbot-wa-auth-"));
|
||||
try {
|
||||
writeCreds(customDir);
|
||||
const cfg = {
|
||||
channels: { whatsapp: { accounts: { work: { authDir: customDir } } } },
|
||||
};
|
||||
|
||||
expect(listWhatsAppAuthDirs(cfg)).toContain(customDir);
|
||||
expect(hasAnyWhatsAppAuth(cfg)).toBe(true);
|
||||
} finally {
|
||||
fs.rmSync(customDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -24,6 +24,15 @@ export function resolveWebCredsBackupPath(authDir: string): string {
|
||||
return path.join(authDir, "creds.json.bak");
|
||||
}
|
||||
|
||||
export function hasWebCredsSync(authDir: string): boolean {
|
||||
try {
|
||||
const stats = fsSync.statSync(resolveWebCredsPath(authDir));
|
||||
return stats.isFile() && stats.size > 1;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function readCredsJsonRaw(filePath: string): string | null {
|
||||
try {
|
||||
if (!fsSync.existsSync(filePath)) return null;
|
||||
|
||||
Reference in New Issue
Block a user