Skip to content

Commit 2d40ae3

Browse files
committed
fix(utils): normalizeE164 returns "" when there are no digits
A From with no digits — a blocked caller the carrier reports as "Anonymous", or a bare "sms://" scheme — normalized to a stray "+", which is not a valid E.164. Since "+" is truthy, deriveSessionKey's `from || "unknown"` fallback never fired and those messages landed in a junk "+" session bucket instead of "unknown". Return "" when there is nothing to normalize so callers fall back as intended; valid numbers are unaffected.
1 parent 6067064 commit 2d40ae3

2 files changed

Lines changed: 27 additions & 3 deletions

File tree

src/utils.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { withEnv } from "./test-utils/env.js";
88
import {
99
CONFIG_DIR,
1010
ensureDir,
11+
normalizeE164,
1112
pinConfigDir,
1213
resolveConfigDir,
1314
resolveHomeDir,
@@ -17,6 +18,25 @@ import {
1718
sleep,
1819
} from "./utils.js";
1920

21+
describe("normalizeE164", () => {
22+
it("normalizes phone-like input into loose E.164", () => {
23+
expect(normalizeE164("+1 (555) 234-5678")).toBe("+15552345678");
24+
expect(normalizeE164("15552345678")).toBe("+15552345678");
25+
expect(normalizeE164("tel:+1-555-234-5678")).toBe("+15552345678");
26+
});
27+
28+
it("returns an empty string when there are no digits to normalize", () => {
29+
// Carriers report blocked caller id as a literal like "Anonymous", and a
30+
// bare scheme such as "sms://" carries no number. These must not collapse to
31+
// a stray "+" (not a valid E.164); returning "" lets callers fall back to
32+
// their own sentinel (e.g. `from || "unknown"`).
33+
expect(normalizeE164("Anonymous")).toBe("");
34+
expect(normalizeE164("sms://")).toBe("");
35+
expect(normalizeE164("")).toBe("");
36+
expect(normalizeE164("+")).toBe("");
37+
});
38+
});
39+
2040
describe("ensureDir", () => {
2141
it("creates nested directory", async () => {
2242
await withTempDir({ prefix: "openclaw-test-" }, async (tmp) => {

src/utils.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,14 @@ export function isRecord(value: unknown): value is Record<string, unknown> {
5656
export function normalizeE164(number: string): string {
5757
const withoutPrefix = number.replace(/^[a-z][a-z0-9-]*:/i, "").trim();
5858
const digits = withoutPrefix.replace(/[^\d+]/g, "");
59-
if (digits.startsWith("+")) {
60-
return `+${digits.slice(1)}`;
59+
const bare = digits.startsWith("+") ? digits.slice(1) : digits;
60+
if (!bare) {
61+
// No digits to normalize (e.g. an "Anonymous" caller id or a bare "sms://"
62+
// scheme). Return "" rather than a stray "+", which is not a valid E.164 and
63+
// silently defeats caller fallbacks like `from || "unknown"`.
64+
return "";
6165
}
62-
return `+${digits}`;
66+
return `+${bare}`;
6367
}
6468

6569
/** Promise-based sleep that clamps timer inputs through the shared timeout resolver. */

0 commit comments

Comments
 (0)