mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:40:44 +00:00
fix(whatsapp): normalize onboarding allowlist numbers
Normalize WhatsApp onboarding allowlist entries to digit-only WhatsApp IDs and reject invalid owner-phone inputs during prompt validation.
This commit is contained in:
@@ -63,6 +63,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- WhatsApp/onboarding: canonicalize setup and pairing allowlist entries to WhatsApp's digit-only phone ids while still accepting E.164, JID, and `whatsapp:` inputs, so personal-phone allowlists match WhatsApp Web sender ids after setup. Thanks @vincentkoc.
|
||||
- Slack/subagents: keep resumed parent `message.send` calls in the originating Slack thread when ambient session thread context is present, and suppress successful silent child completion rows from follow-up findings. Thanks @bek91.
|
||||
- Infra/Windows: skip the POSIX `/tmp/openclaw` preferred path on Windows in `resolvePreferredOpenClawTmpDir` so log files, TTS temp files, and other writes land in `%TEMP%\openclaw-<uid>` instead of `C:\tmp\openclaw`. Fixes #60713. Thanks @juan-flores077.
|
||||
- Gateway/diagnostics: make stuck-session recovery outcome-driven and generation-guarded, add `diagnostics.stuckSessionAbortMs`, and emit structured recovery requested/completed events so stale or skipped recovery no longer looks like a successful abort.
|
||||
|
||||
@@ -172,6 +172,23 @@ describe("whatsapp setup wizard", () => {
|
||||
expectWhatsAppOwnerAllowlistSetup(result.cfg, harness);
|
||||
});
|
||||
|
||||
it("rejects invalid owner numbers during prompt validation", async () => {
|
||||
const harness = createWhatsAppOwnerAllowlistHarness(createQueuedWizardPrompter);
|
||||
|
||||
await runConfigureWithHarness({
|
||||
harness,
|
||||
forceAllowFrom: true,
|
||||
});
|
||||
|
||||
const prompt = harness.text.mock.calls[0]?.[0] as
|
||||
| { validate?: (value: string) => string | undefined }
|
||||
| undefined;
|
||||
expect(prompt?.validate).toEqual(expect.any(Function));
|
||||
expect(prompt?.validate?.("abc")).toBe("Invalid number: abc");
|
||||
expect(prompt?.validate?.("whatsapp:")).toBe("Invalid number: whatsapp:");
|
||||
expect(prompt?.validate?.("+1 (555) 555-0123")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("supports disabled DM policy for separate-phone setup", async () => {
|
||||
const { harness, result } = await runSeparatePhoneFlow({
|
||||
selectValues: ["separate", "disabled"],
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
isWhatsAppGroupJid,
|
||||
isWhatsAppNewsletterJid,
|
||||
looksLikeWhatsAppTargetId,
|
||||
normalizeWhatsAppAllowFromEntry,
|
||||
normalizeWhatsAppMessagingTarget,
|
||||
normalizeWhatsAppTarget,
|
||||
} from "./normalize.js";
|
||||
@@ -69,6 +70,7 @@ export const whatsappPlugin: ChannelPlugin<ResolvedWhatsAppAccount> =
|
||||
createChatChannelPlugin<ResolvedWhatsAppAccount>({
|
||||
pairing: {
|
||||
idLabel: "whatsappSenderId",
|
||||
normalizeAllowEntry: (entry) => normalizeWhatsAppAllowFromEntry(entry) ?? "",
|
||||
},
|
||||
outbound: whatsappChannelOutbound,
|
||||
threading: {
|
||||
|
||||
@@ -29,6 +29,6 @@ describe("whatsapp config accessors", () => {
|
||||
it("normalizes allowFrom entries like the channel plugin", () => {
|
||||
expect(
|
||||
formatWhatsAppConfigAllowFromEntries([" whatsapp:+49123 ", "*", "49124@s.whatsapp.net"]),
|
||||
).toEqual(["+49123", "*", "+49124"]);
|
||||
).toEqual(["49123", "*", "49124"]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -101,11 +101,30 @@ export function normalizeWhatsAppMessagingTarget(raw: string): string | undefine
|
||||
}
|
||||
|
||||
export function normalizeWhatsAppAllowFromEntries(allowFrom: Array<string | number>): string[] {
|
||||
return allowFrom
|
||||
const seen = new Set<string>();
|
||||
const normalized = allowFrom
|
||||
.map((entry) => String(entry).trim())
|
||||
.filter((entry): entry is string => Boolean(entry))
|
||||
.map((entry) => (entry === "*" ? entry : normalizeWhatsAppTarget(entry)))
|
||||
.map(normalizeWhatsAppAllowFromEntry)
|
||||
.filter((entry): entry is string => Boolean(entry));
|
||||
return normalized.filter((entry) => {
|
||||
if (seen.has(entry)) {
|
||||
return false;
|
||||
}
|
||||
seen.add(entry);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
export function normalizeWhatsAppAllowFromEntry(entry: string): string | null {
|
||||
if (entry === "*") {
|
||||
return entry;
|
||||
}
|
||||
const normalized = normalizeWhatsAppTarget(entry);
|
||||
if (!normalized) {
|
||||
return null;
|
||||
}
|
||||
return normalized.startsWith("+") ? normalized.slice(1) : normalized;
|
||||
}
|
||||
|
||||
export function looksLikeWhatsAppTargetId(raw: string): boolean {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export {
|
||||
looksLikeWhatsAppTargetId,
|
||||
normalizeWhatsAppAllowFromEntry,
|
||||
normalizeWhatsAppMessagingTarget,
|
||||
isWhatsAppGroupJid,
|
||||
isWhatsAppNewsletterJid,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import path from "node:path";
|
||||
import {
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
normalizeAllowFromEntries,
|
||||
normalizeE164,
|
||||
pathExists,
|
||||
splitSetupEntries,
|
||||
@@ -15,6 +14,10 @@ import {
|
||||
resolveWhatsAppAccount,
|
||||
resolveWhatsAppAuthDir,
|
||||
} from "./accounts.js";
|
||||
import {
|
||||
normalizeWhatsAppAllowFromEntries,
|
||||
normalizeWhatsAppAllowFromEntry,
|
||||
} from "./normalize-target.js";
|
||||
import { whatsappSetupAdapter } from "./setup-core.js";
|
||||
|
||||
type SetupPrompter = Parameters<NonNullable<ChannelSetupWizard["finalize"]>>[0]["prompter"];
|
||||
@@ -177,7 +180,7 @@ async function promptWhatsAppOwnerAllowFrom(params: {
|
||||
if (!raw) {
|
||||
return "Required";
|
||||
}
|
||||
const normalized = normalizeE164(raw);
|
||||
const normalized = normalizeWhatsAppAllowFromEntry(raw);
|
||||
if (!normalized) {
|
||||
return `Invalid number: ${raw}`;
|
||||
}
|
||||
@@ -185,14 +188,14 @@ async function promptWhatsAppOwnerAllowFrom(params: {
|
||||
},
|
||||
});
|
||||
|
||||
const normalized = normalizeE164(trimPromptText(entry));
|
||||
const normalized = normalizeWhatsAppAllowFromEntry(trimPromptText(entry));
|
||||
if (!normalized) {
|
||||
throw new Error("Invalid WhatsApp owner number (expected E.164 after validation).");
|
||||
}
|
||||
const allowFrom = normalizeAllowFromEntries(
|
||||
[...existingAllowFrom.filter((item) => item !== "*"), normalized],
|
||||
normalizeE164,
|
||||
);
|
||||
const allowFrom = normalizeWhatsAppAllowFromEntries([
|
||||
...existingAllowFrom.filter((item) => item !== "*"),
|
||||
normalized,
|
||||
]);
|
||||
return { normalized, allowFrom };
|
||||
}
|
||||
|
||||
@@ -229,13 +232,13 @@ function parseWhatsAppAllowFromEntries(raw: string): { entries: string[]; invali
|
||||
entries.push("*");
|
||||
continue;
|
||||
}
|
||||
const normalized = normalizeE164(part);
|
||||
const normalized = normalizeWhatsAppAllowFromEntry(part);
|
||||
if (!normalized) {
|
||||
return { entries: [], invalidEntry: part };
|
||||
}
|
||||
entries.push(normalized);
|
||||
}
|
||||
return { entries: normalizeAllowFromEntries(entries, normalizeE164) };
|
||||
return { entries: normalizeWhatsAppAllowFromEntries(entries) };
|
||||
}
|
||||
|
||||
async function promptWhatsAppDmAccess(params: {
|
||||
@@ -313,7 +316,7 @@ async function promptWhatsAppDmAccess(params: {
|
||||
let next = setWhatsAppSelfChatMode(params.cfg, accountId, false);
|
||||
next = setWhatsAppDmPolicy(next, accountId, policy);
|
||||
if (policy === "open") {
|
||||
const allowFrom = normalizeAllowFromEntries(["*", ...existingAllowFrom], normalizeE164);
|
||||
const allowFrom = normalizeWhatsAppAllowFromEntries(["*", ...existingAllowFrom]);
|
||||
next = setWhatsAppAllowFrom(next, accountId, allowFrom.length > 0 ? allowFrom : ["*"]);
|
||||
return next;
|
||||
}
|
||||
|
||||
@@ -24,9 +24,10 @@ type QueuedWizardPrompterFactory<T extends WizardPromptHarness> = (params: {
|
||||
}) => T;
|
||||
|
||||
const WHATSAPP_OWNER_NUMBER_INPUT = "+1 (555) 555-0123";
|
||||
const WHATSAPP_OWNER_NUMBER = "+15555550123";
|
||||
const WHATSAPP_OWNER_NUMBER_E164 = "+15555550123";
|
||||
const WHATSAPP_OWNER_NUMBER = "15555550123";
|
||||
const WHATSAPP_PERSONAL_NUMBER_INPUT = "+1 (555) 111-2222";
|
||||
const WHATSAPP_PERSONAL_NUMBER = "+15551112222";
|
||||
const WHATSAPP_PERSONAL_NUMBER = "15551112222";
|
||||
const WHATSAPP_ACCESS_NOTE_TITLE = "WhatsApp DM access";
|
||||
const WHATSAPP_LOGIN_NOTE_TITLE = "WhatsApp";
|
||||
|
||||
@@ -34,7 +35,7 @@ export function createWhatsAppRootAllowFromConfig(): WhatsAppSetupConfig {
|
||||
return {
|
||||
channels: {
|
||||
whatsapp: {
|
||||
allowFrom: [WHATSAPP_OWNER_NUMBER],
|
||||
allowFrom: [WHATSAPP_OWNER_NUMBER_E164],
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -78,7 +79,7 @@ export function createWhatsAppWorkAccountConfig(
|
||||
whatsapp: {
|
||||
...(params.defaultAccount ? { defaultAccount: params.defaultAccount } : {}),
|
||||
dmPolicy: "disabled",
|
||||
allowFrom: [WHATSAPP_OWNER_NUMBER],
|
||||
allowFrom: [WHATSAPP_OWNER_NUMBER_E164],
|
||||
accounts: {
|
||||
work: {
|
||||
authDir: "/tmp/work",
|
||||
@@ -118,7 +119,7 @@ function expectWhatsAppDmAccess(
|
||||
|
||||
export function expectWhatsAppWorkAccountOpenAccess(cfg: WhatsAppSetupConfig): void {
|
||||
expect(cfg.channels?.whatsapp?.dmPolicy).toBe("disabled");
|
||||
expect(cfg.channels?.whatsapp?.allowFrom).toEqual([WHATSAPP_OWNER_NUMBER]);
|
||||
expect(cfg.channels?.whatsapp?.allowFrom).toEqual([WHATSAPP_OWNER_NUMBER_E164]);
|
||||
expect(cfg.channels?.whatsapp?.accounts?.work?.dmPolicy).toBe("open");
|
||||
expect(cfg.channels?.whatsapp?.accounts?.work?.allowFrom).toEqual(["*", WHATSAPP_OWNER_NUMBER]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user