refactor: share bluebubbles multipart helpers

This commit is contained in:
Peter Steinberger 2026-03-13 22:54:16 +00:00
parent d7aa3cc1c3
commit 9b0e333f2c
4 changed files with 21 additions and 40 deletions

View File

@ -2,7 +2,7 @@ import crypto from "node:crypto";
import path from "node:path";
import type { OpenClawConfig } from "openclaw/plugin-sdk/bluebubbles";
import { resolveBlueBubblesServerAccount } from "./account-resolve.js";
import { postMultipartFormData } from "./multipart.js";
import { assertMultipartActionOk, postMultipartFormData } from "./multipart.js";
import {
getCachedBlueBubblesPrivateApiStatus,
isBlueBubblesPrivateApiStatusEnabled,
@ -262,12 +262,7 @@ export async function sendBlueBubblesAttachment(params: {
timeoutMs: opts.timeoutMs ?? 60_000, // longer timeout for file uploads
});
if (!res.ok) {
const errorText = await res.text();
throw new Error(
`BlueBubbles attachment send failed (${res.status}): ${errorText || "unknown"}`,
);
}
await assertMultipartActionOk(res, "attachment send");
const responseBody = await res.text();
if (!responseBody) {

View File

@ -29,6 +29,11 @@ describe("chat", () => {
});
}
function mockTwoOkTextResponses() {
mockOkTextResponse();
mockOkTextResponse();
}
async function expectCalledUrlIncludesPassword(params: {
password: string;
invoke: () => Promise<void>;
@ -198,15 +203,7 @@ describe("chat", () => {
});
it("uses POST for start and DELETE for stop", async () => {
mockFetch
.mockResolvedValueOnce({
ok: true,
text: () => Promise.resolve(""),
})
.mockResolvedValueOnce({
ok: true,
text: () => Promise.resolve(""),
});
mockTwoOkTextResponses();
await sendBlueBubblesTyping("iMessage;-;+15551234567", true, {
serverUrl: "http://localhost:1234",
@ -442,15 +439,7 @@ describe("chat", () => {
});
it("adds and removes participant using matching endpoint", async () => {
mockFetch
.mockResolvedValueOnce({
ok: true,
text: () => Promise.resolve(""),
})
.mockResolvedValueOnce({
ok: true,
text: () => Promise.resolve(""),
});
mockTwoOkTextResponses();
await addBlueBubblesParticipant("chat-guid", "+15551234567", {
serverUrl: "http://localhost:1234",

View File

@ -2,7 +2,7 @@ import crypto from "node:crypto";
import path from "node:path";
import type { OpenClawConfig } from "openclaw/plugin-sdk/bluebubbles";
import { resolveBlueBubblesServerAccount } from "./account-resolve.js";
import { postMultipartFormData } from "./multipart.js";
import { assertMultipartActionOk, postMultipartFormData } from "./multipart.js";
import { getCachedBlueBubblesPrivateApiStatus } from "./probe.js";
import { blueBubblesFetchWithTimeout, buildBlueBubblesApiUrl } from "./types.js";
@ -26,14 +26,6 @@ function assertPrivateApiEnabled(accountId: string, feature: string): void {
}
}
async function assertBlueBubblesActionOk(response: Response, action: string): Promise<void> {
if (response.ok) {
return;
}
const errorText = await response.text().catch(() => "");
throw new Error(`BlueBubbles ${action} failed (${response.status}): ${errorText || "unknown"}`);
}
function resolvePartIndex(partIndex: number | undefined): number {
return typeof partIndex === "number" ? partIndex : 0;
}
@ -63,7 +55,7 @@ async function sendBlueBubblesChatEndpointRequest(params: {
{ method: params.method },
params.opts.timeoutMs,
);
await assertBlueBubblesActionOk(res, params.action);
await assertMultipartActionOk(res, params.action);
}
async function sendPrivateApiJsonRequest(params: {
@ -89,7 +81,7 @@ async function sendPrivateApiJsonRequest(params: {
}
const res = await blueBubblesFetchWithTimeout(url, request, params.opts.timeoutMs);
await assertBlueBubblesActionOk(res, params.action);
await assertMultipartActionOk(res, params.action);
}
export async function markBlueBubblesChatRead(
@ -327,8 +319,5 @@ export async function setGroupIconBlueBubbles(
timeoutMs: opts.timeoutMs ?? 60_000, // longer timeout for file uploads
});
if (!res.ok) {
const errorText = await res.text().catch(() => "");
throw new Error(`BlueBubbles setGroupIcon failed (${res.status}): ${errorText || "unknown"}`);
}
await assertMultipartActionOk(res, "setGroupIcon");
}

View File

@ -30,3 +30,11 @@ export async function postMultipartFormData(params: {
params.timeoutMs,
);
}
export async function assertMultipartActionOk(response: Response, action: string): Promise<void> {
if (response.ok) {
return;
}
const errorText = await response.text().catch(() => "");
throw new Error(`BlueBubbles ${action} failed (${response.status}): ${errorText || "unknown"}`);
}