Skip to content

Commit cc1b9bd

Browse files
committed
fix: align BlueBubbles private-api null fallback + warning (#23459) (thanks @echoVic)
1 parent 34f2b6a commit cc1b9bd

File tree

3 files changed

+42
-0
lines changed

3 files changed

+42
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ Docs: https://docs.openclaw.ai
6262
- Memory/QMD: add optional `memory.qmd.mcporter` search routing so QMD `query/search/vsearch` can run through mcporter keep-alive flows (including multi-collection paths) to reduce cold starts, while keeping searches on agent-scoped QMD state for consistent recall. (#19617) Thanks @nicole-luxe and @vignesh07.
6363
- Chat/UI: strip inline reply/audio directive tags (`[[reply_to_current]]`, `[[reply_to:<id>]]`, `[[audio_as_voice]]`) from displayed chat history, live chat event output, and session preview snippets so control tags no longer leak into user-visible surfaces.
6464
- BlueBubbles/DM history: restore DM backfill context with account-scoped rolling history, bounded backfill retries, and safer history payload limits. (#20302) Thanks @Ryan-Haines.
65+
- BlueBubbles/Private API cache: treat unknown (`null`) private-API cache status as disabled for send/attachment/reply flows to avoid stale-cache 500s, and log a warning when reply/effect features are requested while capability is unknown. (#23459) Thanks @echoVic.
6566
- BlueBubbles/Webhooks: accept inbound/reaction webhook payloads when BlueBubbles omits `handle` but provides DM `chatGuid`, and harden payload extraction for array/string-wrapped message bodies so valid webhook events no longer get rejected as unparseable. (#23275) Thanks @toph31.
6667
- Security/Audit: add `openclaw security audit` finding `gateway.nodes.allow_commands_dangerous` for risky `gateway.nodes.allowCommands` overrides, with severity upgraded to critical on remote gateway exposure.
6768
- Gateway/Control plane: reduce cross-client write limiter contention by adding `connId` fallback keying when device ID and client IP are both unavailable.

extensions/bluebubbles/src/send.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,7 @@ describe("send", () => {
527527
});
528528

529529
it("uses private-api when reply metadata is present", async () => {
530+
vi.mocked(getCachedBlueBubblesPrivateApiStatus).mockReturnValueOnce(true);
530531
mockResolvedHandleTarget();
531532
mockSendResponse({ data: { guid: "msg-uuid-124" } });
532533

@@ -568,6 +569,7 @@ describe("send", () => {
568569
});
569570

570571
it("normalizes effect names and uses private-api for effects", async () => {
572+
vi.mocked(getCachedBlueBubblesPrivateApiStatus).mockReturnValueOnce(true);
571573
mockResolvedHandleTarget();
572574
mockSendResponse({ data: { guid: "msg-uuid-125" } });
573575

@@ -586,6 +588,34 @@ describe("send", () => {
586588
expect(body.effectId).toBe("com.apple.MobileSMS.expressivesend.invisibleink");
587589
});
588590

591+
it("warns and downgrades private-api features when status is unknown", async () => {
592+
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
593+
mockResolvedHandleTarget();
594+
mockSendResponse({ data: { guid: "msg-uuid-unknown" } });
595+
596+
try {
597+
const result = await sendMessageBlueBubbles("+15551234567", "Reply fallback", {
598+
serverUrl: "http://localhost:1234",
599+
password: "test",
600+
replyToMessageGuid: "reply-guid-123",
601+
effectId: "invisible ink",
602+
});
603+
604+
expect(result.messageId).toBe("msg-uuid-unknown");
605+
expect(warnSpy).toHaveBeenCalledTimes(1);
606+
expect(warnSpy.mock.calls[0]?.[0]).toContain("Private API status unknown");
607+
608+
const sendCall = mockFetch.mock.calls[1];
609+
const body = JSON.parse(sendCall[1].body);
610+
expect(body.method).toBeUndefined();
611+
expect(body.selectedMessageGuid).toBeUndefined();
612+
expect(body.partIndex).toBeUndefined();
613+
expect(body.effectId).toBeUndefined();
614+
} finally {
615+
warnSpy.mockRestore();
616+
}
617+
});
618+
589619
it("sends message with chat_guid target directly", async () => {
590620
mockFetch.mockResolvedValueOnce({
591621
ok: true,

extensions/bluebubbles/src/send.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,17 @@ export async function sendMessageBlueBubbles(
379379
"BlueBubbles send failed: reply/effect requires Private API, but it is disabled on the BlueBubbles server.",
380380
);
381381
}
382+
if (needsPrivateApi && privateApiStatus === null) {
383+
const requested = [
384+
wantsReplyThread ? "reply threading" : null,
385+
wantsEffect ? "message effects" : null,
386+
]
387+
.filter(Boolean)
388+
.join(" + ");
389+
console.warn(
390+
`[bluebubbles] Private API status unknown; sending without ${requested}. Run a status probe to restore private-api features.`,
391+
);
392+
}
382393
const payload: Record<string, unknown> = {
383394
chatGuid,
384395
tempGuid: crypto.randomUUID(),

0 commit comments

Comments
 (0)