@openclaw/bluebubbles 2026.3.2 → 2026.3.8-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  import { EventEmitter } from "node:events";
2
2
  import type { IncomingMessage, ServerResponse } from "node:http";
3
- import type { OpenClawConfig, PluginRuntime } from "openclaw/plugin-sdk";
3
+ import type { OpenClawConfig, PluginRuntime } from "openclaw/plugin-sdk/bluebubbles";
4
4
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
5
5
  import { createPluginRuntimeMock } from "../../test-utils/plugin-runtime-mock.js";
6
6
  import type { ResolvedBlueBubblesAccount } from "./accounts.js";
@@ -166,7 +166,7 @@ function createMockAccount(
166
166
  configured: true,
167
167
  config: {
168
168
  serverUrl: "http://localhost:1234",
169
- password: "test-password",
169
+ password: "test-password", // pragma: allowlist secret
170
170
  dmPolicy: "open",
171
171
  groupPolicy: "open",
172
172
  allowFrom: [],
@@ -261,6 +261,47 @@ describe("BlueBubbles webhook monitor", () => {
261
261
  unregister?.();
262
262
  });
263
263
 
264
+ function setupWebhookTarget(params?: {
265
+ account?: ResolvedBlueBubblesAccount;
266
+ config?: OpenClawConfig;
267
+ core?: PluginRuntime;
268
+ statusSink?: (event: unknown) => void;
269
+ }) {
270
+ const account = params?.account ?? createMockAccount();
271
+ const config = params?.config ?? {};
272
+ const core = params?.core ?? createMockRuntime();
273
+ setBlueBubblesRuntime(core);
274
+ unregister = registerBlueBubblesWebhookTarget({
275
+ account,
276
+ config,
277
+ runtime: { log: vi.fn(), error: vi.fn() },
278
+ core,
279
+ path: "/bluebubbles-webhook",
280
+ statusSink: params?.statusSink,
281
+ });
282
+ return { account, config, core };
283
+ }
284
+
285
+ function createNewMessagePayload(dataOverrides: Record<string, unknown> = {}) {
286
+ return {
287
+ type: "new-message",
288
+ data: {
289
+ text: "hello",
290
+ handle: { address: "+15551234567" },
291
+ isGroup: false,
292
+ isFromMe: false,
293
+ guid: "msg-1",
294
+ ...dataOverrides,
295
+ },
296
+ };
297
+ }
298
+
299
+ function setRequestRemoteAddress(req: IncomingMessage, remoteAddress: string) {
300
+ (req as unknown as { socket: { remoteAddress: string } }).socket = {
301
+ remoteAddress,
302
+ };
303
+ }
304
+
264
305
  describe("webhook parsing + auth handling", () => {
265
306
  it("rejects non-POST requests", async () => {
266
307
  const account = createMockAccount();
@@ -286,30 +327,8 @@ describe("BlueBubbles webhook monitor", () => {
286
327
  });
287
328
 
288
329
  it("accepts POST requests with valid JSON payload", async () => {
289
- const account = createMockAccount();
290
- const config: OpenClawConfig = {};
291
- const core = createMockRuntime();
292
- setBlueBubblesRuntime(core);
293
-
294
- unregister = registerBlueBubblesWebhookTarget({
295
- account,
296
- config,
297
- runtime: { log: vi.fn(), error: vi.fn() },
298
- core,
299
- path: "/bluebubbles-webhook",
300
- });
301
-
302
- const payload = {
303
- type: "new-message",
304
- data: {
305
- text: "hello",
306
- handle: { address: "+15551234567" },
307
- isGroup: false,
308
- isFromMe: false,
309
- guid: "msg-1",
310
- date: Date.now(),
311
- },
312
- };
330
+ setupWebhookTarget();
331
+ const payload = createNewMessagePayload({ date: Date.now() });
313
332
 
314
333
  const req = createMockRequest("POST", "/bluebubbles-webhook", payload);
315
334
  const res = createMockResponse();
@@ -345,30 +364,8 @@ describe("BlueBubbles webhook monitor", () => {
345
364
  });
346
365
 
347
366
  it("accepts URL-encoded payload wrappers", async () => {
348
- const account = createMockAccount();
349
- const config: OpenClawConfig = {};
350
- const core = createMockRuntime();
351
- setBlueBubblesRuntime(core);
352
-
353
- unregister = registerBlueBubblesWebhookTarget({
354
- account,
355
- config,
356
- runtime: { log: vi.fn(), error: vi.fn() },
357
- core,
358
- path: "/bluebubbles-webhook",
359
- });
360
-
361
- const payload = {
362
- type: "new-message",
363
- data: {
364
- text: "hello",
365
- handle: { address: "+15551234567" },
366
- isGroup: false,
367
- isFromMe: false,
368
- guid: "msg-1",
369
- date: Date.now(),
370
- },
371
- };
367
+ setupWebhookTarget();
368
+ const payload = createNewMessagePayload({ date: Date.now() });
372
369
  const encodedBody = new URLSearchParams({
373
370
  payload: JSON.stringify(payload),
374
371
  }).toString();
@@ -458,32 +455,15 @@ describe("BlueBubbles webhook monitor", () => {
458
455
 
459
456
  it("authenticates via password query parameter", async () => {
460
457
  const account = createMockAccount({ password: "secret-token" });
461
- const config: OpenClawConfig = {};
462
- const core = createMockRuntime();
463
- setBlueBubblesRuntime(core);
464
458
 
465
459
  // Mock non-localhost request
466
- const req = createMockRequest("POST", "/bluebubbles-webhook?password=secret-token", {
467
- type: "new-message",
468
- data: {
469
- text: "hello",
470
- handle: { address: "+15551234567" },
471
- isGroup: false,
472
- isFromMe: false,
473
- guid: "msg-1",
474
- },
475
- });
476
- (req as unknown as { socket: { remoteAddress: string } }).socket = {
477
- remoteAddress: "192.168.1.100",
478
- };
479
-
480
- unregister = registerBlueBubblesWebhookTarget({
481
- account,
482
- config,
483
- runtime: { log: vi.fn(), error: vi.fn() },
484
- core,
485
- path: "/bluebubbles-webhook",
486
- });
460
+ const req = createMockRequest(
461
+ "POST",
462
+ "/bluebubbles-webhook?password=secret-token",
463
+ createNewMessagePayload(),
464
+ );
465
+ setRequestRemoteAddress(req, "192.168.1.100");
466
+ setupWebhookTarget({ account });
487
467
 
488
468
  const res = createMockResponse();
489
469
  const handled = await handleBlueBubblesWebhookRequest(req, res);
@@ -494,36 +474,15 @@ describe("BlueBubbles webhook monitor", () => {
494
474
 
495
475
  it("authenticates via x-password header", async () => {
496
476
  const account = createMockAccount({ password: "secret-token" });
497
- const config: OpenClawConfig = {};
498
- const core = createMockRuntime();
499
- setBlueBubblesRuntime(core);
500
477
 
501
478
  const req = createMockRequest(
502
479
  "POST",
503
480
  "/bluebubbles-webhook",
504
- {
505
- type: "new-message",
506
- data: {
507
- text: "hello",
508
- handle: { address: "+15551234567" },
509
- isGroup: false,
510
- isFromMe: false,
511
- guid: "msg-1",
512
- },
513
- },
514
- { "x-password": "secret-token" },
481
+ createNewMessagePayload(),
482
+ { "x-password": "secret-token" }, // pragma: allowlist secret
515
483
  );
516
- (req as unknown as { socket: { remoteAddress: string } }).socket = {
517
- remoteAddress: "192.168.1.100",
518
- };
519
-
520
- unregister = registerBlueBubblesWebhookTarget({
521
- account,
522
- config,
523
- runtime: { log: vi.fn(), error: vi.fn() },
524
- core,
525
- path: "/bluebubbles-webhook",
526
- });
484
+ setRequestRemoteAddress(req, "192.168.1.100");
485
+ setupWebhookTarget({ account });
527
486
 
528
487
  const res = createMockResponse();
529
488
  const handled = await handleBlueBubblesWebhookRequest(req, res);
@@ -534,31 +493,13 @@ describe("BlueBubbles webhook monitor", () => {
534
493
 
535
494
  it("rejects unauthorized requests with wrong password", async () => {
536
495
  const account = createMockAccount({ password: "secret-token" });
537
- const config: OpenClawConfig = {};
538
- const core = createMockRuntime();
539
- setBlueBubblesRuntime(core);
540
-
541
- const req = createMockRequest("POST", "/bluebubbles-webhook?password=wrong-token", {
542
- type: "new-message",
543
- data: {
544
- text: "hello",
545
- handle: { address: "+15551234567" },
546
- isGroup: false,
547
- isFromMe: false,
548
- guid: "msg-1",
549
- },
550
- });
551
- (req as unknown as { socket: { remoteAddress: string } }).socket = {
552
- remoteAddress: "192.168.1.100",
553
- };
554
-
555
- unregister = registerBlueBubblesWebhookTarget({
556
- account,
557
- config,
558
- runtime: { log: vi.fn(), error: vi.fn() },
559
- core,
560
- path: "/bluebubbles-webhook",
561
- });
496
+ const req = createMockRequest(
497
+ "POST",
498
+ "/bluebubbles-webhook?password=wrong-token",
499
+ createNewMessagePayload(),
500
+ );
501
+ setRequestRemoteAddress(req, "192.168.1.100");
502
+ setupWebhookTarget({ account });
562
503
 
563
504
  const res = createMockResponse();
564
505
  const handled = await handleBlueBubblesWebhookRequest(req, res);
@@ -770,32 +711,14 @@ describe("BlueBubbles webhook monitor", () => {
770
711
  const { resolveChatGuidForTarget } = await import("./send.js");
771
712
  vi.mocked(resolveChatGuidForTarget).mockClear();
772
713
 
773
- const account = createMockAccount({ groupPolicy: "open" });
774
- const config: OpenClawConfig = {};
775
- const core = createMockRuntime();
776
- setBlueBubblesRuntime(core);
777
-
778
- unregister = registerBlueBubblesWebhookTarget({
779
- account,
780
- config,
781
- runtime: { log: vi.fn(), error: vi.fn() },
782
- core,
783
- path: "/bluebubbles-webhook",
714
+ setupWebhookTarget({ account: createMockAccount({ groupPolicy: "open" }) });
715
+ const payload = createNewMessagePayload({
716
+ text: "hello from group",
717
+ isGroup: true,
718
+ chatId: "123",
719
+ date: Date.now(),
784
720
  });
785
721
 
786
- const payload = {
787
- type: "new-message",
788
- data: {
789
- text: "hello from group",
790
- handle: { address: "+15551234567" },
791
- isGroup: true,
792
- isFromMe: false,
793
- guid: "msg-1",
794
- chatId: "123",
795
- date: Date.now(),
796
- },
797
- };
798
-
799
722
  const req = createMockRequest("POST", "/bluebubbles-webhook", payload);
800
723
  const res = createMockResponse();
801
724
 
@@ -819,32 +742,14 @@ describe("BlueBubbles webhook monitor", () => {
819
742
  return EMPTY_DISPATCH_RESULT;
820
743
  });
821
744
 
822
- const account = createMockAccount({ groupPolicy: "open" });
823
- const config: OpenClawConfig = {};
824
- const core = createMockRuntime();
825
- setBlueBubblesRuntime(core);
826
-
827
- unregister = registerBlueBubblesWebhookTarget({
828
- account,
829
- config,
830
- runtime: { log: vi.fn(), error: vi.fn() },
831
- core,
832
- path: "/bluebubbles-webhook",
745
+ setupWebhookTarget({ account: createMockAccount({ groupPolicy: "open" }) });
746
+ const payload = createNewMessagePayload({
747
+ text: "hello from group",
748
+ isGroup: true,
749
+ chat: { chatGuid: "iMessage;+;chat123456" },
750
+ date: Date.now(),
833
751
  });
834
752
 
835
- const payload = {
836
- type: "new-message",
837
- data: {
838
- text: "hello from group",
839
- handle: { address: "+15551234567" },
840
- isGroup: true,
841
- isFromMe: false,
842
- guid: "msg-1",
843
- chat: { chatGuid: "iMessage;+;chat123456" },
844
- date: Date.now(),
845
- },
846
- };
847
-
848
753
  const req = createMockRequest("POST", "/bluebubbles-webhook", payload);
849
754
  const res = createMockResponse();
850
755
 
@@ -1,4 +1,4 @@
1
- import type { OpenClawConfig } from "openclaw/plugin-sdk";
1
+ import type { OpenClawConfig } from "openclaw/plugin-sdk/bluebubbles";
2
2
  import { afterEach, describe, expect, it } from "vitest";
3
3
  import { createEmptyPluginRegistry } from "../../../src/plugins/registry.js";
4
4
  import { setActivePluginRegistry } from "../../../src/plugins/runtime.js";
@@ -1,7 +1,7 @@
1
- import type { WizardPrompter } from "openclaw/plugin-sdk";
1
+ import type { WizardPrompter } from "openclaw/plugin-sdk/bluebubbles";
2
2
  import { describe, expect, it, vi } from "vitest";
3
3
 
4
- vi.mock("openclaw/plugin-sdk", () => ({
4
+ vi.mock("openclaw/plugin-sdk/bluebubbles", () => ({
5
5
  DEFAULT_ACCOUNT_ID: "default",
6
6
  addWildcardAllowFrom: vi.fn(),
7
7
  formatDocsLink: (_url: string, fallback: string) => fallback,
@@ -23,6 +23,10 @@ vi.mock("openclaw/plugin-sdk", () => ({
23
23
  );
24
24
  },
25
25
  mergeAllowFromEntries: (_existing: unknown, entries: string[]) => entries,
26
+ createAccountListHelpers: () => ({
27
+ listAccountIds: () => ["default"],
28
+ resolveDefaultAccountId: () => "default",
29
+ }),
26
30
  normalizeSecretInputString: (value: unknown) => {
27
31
  if (typeof value !== "string") {
28
32
  return undefined;
@@ -33,6 +37,10 @@ vi.mock("openclaw/plugin-sdk", () => ({
33
37
  normalizeAccountId: (value?: string | null) =>
34
38
  value && value.trim().length > 0 ? value : "default",
35
39
  promptAccountId: vi.fn(),
40
+ resolveAccountIdForConfigure: async (params: {
41
+ accountOverride?: string;
42
+ defaultAccountId: string;
43
+ }) => params.accountOverride?.trim() || params.defaultAccountId,
36
44
  }));
37
45
 
38
46
  describe("bluebubbles onboarding SecretInput", () => {
package/src/onboarding.ts CHANGED
@@ -4,20 +4,21 @@ import type {
4
4
  OpenClawConfig,
5
5
  DmPolicy,
6
6
  WizardPrompter,
7
- } from "openclaw/plugin-sdk";
7
+ } from "openclaw/plugin-sdk/bluebubbles";
8
8
  import {
9
9
  DEFAULT_ACCOUNT_ID,
10
- addWildcardAllowFrom,
11
10
  formatDocsLink,
12
11
  mergeAllowFromEntries,
13
12
  normalizeAccountId,
14
- promptAccountId,
15
- } from "openclaw/plugin-sdk";
13
+ resolveAccountIdForConfigure,
14
+ setTopLevelChannelDmPolicyWithAllowFrom,
15
+ } from "openclaw/plugin-sdk/bluebubbles";
16
16
  import {
17
17
  listBlueBubblesAccountIds,
18
18
  resolveBlueBubblesAccount,
19
19
  resolveDefaultBlueBubblesAccountId,
20
20
  } from "./accounts.js";
21
+ import { applyBlueBubblesConnectionConfig } from "./config-apply.js";
21
22
  import { hasConfiguredSecretInput, normalizeSecretInputString } from "./secret-input.js";
22
23
  import { parseBlueBubblesAllowTarget } from "./targets.js";
23
24
  import { normalizeBlueBubblesServerUrl } from "./types.js";
@@ -25,19 +26,11 @@ import { normalizeBlueBubblesServerUrl } from "./types.js";
25
26
  const channel = "bluebubbles" as const;
26
27
 
27
28
  function setBlueBubblesDmPolicy(cfg: OpenClawConfig, dmPolicy: DmPolicy): OpenClawConfig {
28
- const allowFrom =
29
- dmPolicy === "open" ? addWildcardAllowFrom(cfg.channels?.bluebubbles?.allowFrom) : undefined;
30
- return {
31
- ...cfg,
32
- channels: {
33
- ...cfg.channels,
34
- bluebubbles: {
35
- ...cfg.channels?.bluebubbles,
36
- dmPolicy,
37
- ...(allowFrom ? { allowFrom } : {}),
38
- },
39
- },
40
- };
29
+ return setTopLevelChannelDmPolicyWithAllowFrom({
30
+ cfg,
31
+ channel: "bluebubbles",
32
+ dmPolicy,
33
+ });
41
34
  }
42
35
 
43
36
  function setBlueBubblesAllowFrom(
@@ -159,21 +152,16 @@ export const blueBubblesOnboardingAdapter: ChannelOnboardingAdapter = {
159
152
  };
160
153
  },
161
154
  configure: async ({ cfg, prompter, accountOverrides, shouldPromptAccountIds }) => {
162
- const blueBubblesOverride = accountOverrides.bluebubbles?.trim();
163
155
  const defaultAccountId = resolveDefaultBlueBubblesAccountId(cfg);
164
- let accountId = blueBubblesOverride
165
- ? normalizeAccountId(blueBubblesOverride)
166
- : defaultAccountId;
167
- if (shouldPromptAccountIds && !blueBubblesOverride) {
168
- accountId = await promptAccountId({
169
- cfg,
170
- prompter,
171
- label: "BlueBubbles",
172
- currentId: accountId,
173
- listAccountIds: listBlueBubblesAccountIds,
174
- defaultAccountId,
175
- });
176
- }
156
+ const accountId = await resolveAccountIdForConfigure({
157
+ cfg,
158
+ prompter,
159
+ label: "BlueBubbles",
160
+ accountOverride: accountOverrides.bluebubbles,
161
+ shouldPromptAccountIds,
162
+ listAccountIds: listBlueBubblesAccountIds,
163
+ defaultAccountId,
164
+ });
177
165
 
178
166
  let next = cfg;
179
167
  const resolvedAccount = resolveBlueBubblesAccount({ cfg: next, accountId });
@@ -283,42 +271,16 @@ export const blueBubblesOnboardingAdapter: ChannelOnboardingAdapter = {
283
271
  }
284
272
 
285
273
  // Apply config
286
- if (accountId === DEFAULT_ACCOUNT_ID) {
287
- next = {
288
- ...next,
289
- channels: {
290
- ...next.channels,
291
- bluebubbles: {
292
- ...next.channels?.bluebubbles,
293
- enabled: true,
294
- serverUrl,
295
- password,
296
- webhookPath,
297
- },
298
- },
299
- };
300
- } else {
301
- next = {
302
- ...next,
303
- channels: {
304
- ...next.channels,
305
- bluebubbles: {
306
- ...next.channels?.bluebubbles,
307
- enabled: true,
308
- accounts: {
309
- ...next.channels?.bluebubbles?.accounts,
310
- [accountId]: {
311
- ...next.channels?.bluebubbles?.accounts?.[accountId],
312
- enabled: next.channels?.bluebubbles?.accounts?.[accountId]?.enabled ?? true,
313
- serverUrl,
314
- password,
315
- webhookPath,
316
- },
317
- },
318
- },
319
- },
320
- };
321
- }
274
+ next = applyBlueBubblesConnectionConfig({
275
+ cfg: next,
276
+ accountId,
277
+ patch: {
278
+ serverUrl,
279
+ password,
280
+ webhookPath,
281
+ },
282
+ accountEnabled: "preserve-or-true",
283
+ });
322
284
 
323
285
  await prompter.note(
324
286
  [
package/src/probe.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { BaseProbeResult } from "openclaw/plugin-sdk";
1
+ import type { BaseProbeResult } from "openclaw/plugin-sdk/bluebubbles";
2
2
  import { normalizeSecretInputString } from "./secret-input.js";
3
3
  import { buildBlueBubblesApiUrl, blueBubblesFetchWithTimeout } from "./types.js";
4
4
 
package/src/reactions.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { OpenClawConfig } from "openclaw/plugin-sdk";
1
+ import type { OpenClawConfig } from "openclaw/plugin-sdk/bluebubbles";
2
2
  import { resolveBlueBubblesServerAccount } from "./account-resolve.js";
3
3
  import { getCachedBlueBubblesPrivateApiStatus } from "./probe.js";
4
4
  import { blueBubblesFetchWithTimeout, buildBlueBubblesApiUrl } from "./types.js";
@@ -1,12 +1 @@
1
- export function resolveRequestUrl(input: RequestInfo | URL): string {
2
- if (typeof input === "string") {
3
- return input;
4
- }
5
- if (input instanceof URL) {
6
- return input.toString();
7
- }
8
- if (typeof input === "object" && input && "url" in input && typeof input.url === "string") {
9
- return input.url;
10
- }
11
- return String(input);
12
- }
1
+ export { resolveRequestUrl } from "openclaw/plugin-sdk/bluebubbles";
package/src/runtime.ts CHANGED
@@ -1,31 +1,26 @@
1
- import type { PluginRuntime } from "openclaw/plugin-sdk";
1
+ import type { PluginRuntime } from "openclaw/plugin-sdk/bluebubbles";
2
+ import { createPluginRuntimeStore } from "openclaw/plugin-sdk/compat";
2
3
 
3
- let runtime: PluginRuntime | null = null;
4
+ const runtimeStore = createPluginRuntimeStore<PluginRuntime>("BlueBubbles runtime not initialized");
4
5
  type LegacyRuntimeLogShape = { log?: (message: string) => void };
5
-
6
- export function setBlueBubblesRuntime(next: PluginRuntime): void {
7
- runtime = next;
8
- }
6
+ export const setBlueBubblesRuntime = runtimeStore.setRuntime;
9
7
 
10
8
  export function clearBlueBubblesRuntime(): void {
11
- runtime = null;
9
+ runtimeStore.clearRuntime();
12
10
  }
13
11
 
14
12
  export function tryGetBlueBubblesRuntime(): PluginRuntime | null {
15
- return runtime;
13
+ return runtimeStore.tryGetRuntime();
16
14
  }
17
15
 
18
16
  export function getBlueBubblesRuntime(): PluginRuntime {
19
- if (!runtime) {
20
- throw new Error("BlueBubbles runtime not initialized");
21
- }
22
- return runtime;
17
+ return runtimeStore.getRuntime();
23
18
  }
24
19
 
25
20
  export function warnBlueBubbles(message: string): void {
26
21
  const formatted = `[bluebubbles] ${message}`;
27
22
  // Backward-compatible with tests/legacy injections that pass { log }.
28
- const log = (runtime as unknown as LegacyRuntimeLogShape | null)?.log;
23
+ const log = (runtimeStore.tryGetRuntime() as unknown as LegacyRuntimeLogShape | null)?.log;
29
24
  if (typeof log === "function") {
30
25
  log(formatted);
31
26
  return;
@@ -1,19 +1,13 @@
1
1
  import {
2
+ buildSecretInputSchema,
2
3
  hasConfiguredSecretInput,
3
4
  normalizeResolvedSecretInputString,
4
5
  normalizeSecretInputString,
5
- } from "openclaw/plugin-sdk";
6
- import { z } from "zod";
6
+ } from "openclaw/plugin-sdk/bluebubbles";
7
7
 
8
- export { hasConfiguredSecretInput, normalizeResolvedSecretInputString, normalizeSecretInputString };
9
-
10
- export function buildSecretInputSchema() {
11
- return z.union([
12
- z.string(),
13
- z.object({
14
- source: z.enum(["env", "file", "exec"]),
15
- provider: z.string().min(1),
16
- id: z.string().min(1),
17
- }),
18
- ]);
19
- }
8
+ export {
9
+ buildSecretInputSchema,
10
+ hasConfiguredSecretInput,
11
+ normalizeResolvedSecretInputString,
12
+ normalizeSecretInputString,
13
+ };
package/src/send.test.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { PluginRuntime } from "openclaw/plugin-sdk";
1
+ import type { PluginRuntime } from "openclaw/plugin-sdk/bluebubbles";
2
2
  import { beforeEach, describe, expect, it, vi } from "vitest";
3
3
  import "./test-mocks.js";
4
4
  import { getCachedBlueBubblesPrivateApiStatus } from "./probe.js";
package/src/send.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import crypto from "node:crypto";
2
- import type { OpenClawConfig } from "openclaw/plugin-sdk";
3
- import { stripMarkdown } from "openclaw/plugin-sdk";
2
+ import type { OpenClawConfig } from "openclaw/plugin-sdk/bluebubbles";
3
+ import { stripMarkdown } from "openclaw/plugin-sdk/bluebubbles";
4
4
  import { resolveBlueBubblesAccount } from "./accounts.js";
5
5
  import {
6
6
  getCachedBlueBubblesPrivateApiStatus,
@@ -108,6 +108,19 @@ function resolvePrivateApiDecision(params: {
108
108
  };
109
109
  }
110
110
 
111
+ async function parseBlueBubblesMessageResponse(res: Response): Promise<BlueBubblesSendResult> {
112
+ const body = await res.text();
113
+ if (!body) {
114
+ return { messageId: "ok" };
115
+ }
116
+ try {
117
+ const parsed = JSON.parse(body) as unknown;
118
+ return { messageId: extractBlueBubblesMessageId(parsed) };
119
+ } catch {
120
+ return { messageId: "ok" };
121
+ }
122
+ }
123
+
111
124
  type BlueBubblesChatRecord = Record<string, unknown>;
112
125
 
113
126
  function extractChatGuid(chat: BlueBubblesChatRecord): string | null {
@@ -342,16 +355,7 @@ async function createNewChatWithMessage(params: {
342
355
  }
343
356
  throw new Error(`BlueBubbles create chat failed (${res.status}): ${errorText || "unknown"}`);
344
357
  }
345
- const body = await res.text();
346
- if (!body) {
347
- return { messageId: "ok" };
348
- }
349
- try {
350
- const parsed = JSON.parse(body) as unknown;
351
- return { messageId: extractBlueBubblesMessageId(parsed) };
352
- } catch {
353
- return { messageId: "ok" };
354
- }
358
+ return parseBlueBubblesMessageResponse(res);
355
359
  }
356
360
 
357
361
  export async function sendMessageBlueBubbles(
@@ -464,14 +468,5 @@ export async function sendMessageBlueBubbles(
464
468
  const errorText = await res.text();
465
469
  throw new Error(`BlueBubbles send failed (${res.status}): ${errorText || "unknown"}`);
466
470
  }
467
- const body = await res.text();
468
- if (!body) {
469
- return { messageId: "ok" };
470
- }
471
- try {
472
- const parsed = JSON.parse(body) as unknown;
473
- return { messageId: extractBlueBubblesMessageId(parsed) };
474
- } catch {
475
- return { messageId: "ok" };
476
- }
471
+ return parseBlueBubblesMessageResponse(res);
477
472
  }
package/src/targets.ts CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  type ParsedChatTarget,
6
6
  resolveServicePrefixedAllowTarget,
7
7
  resolveServicePrefixedTarget,
8
- } from "openclaw/plugin-sdk";
8
+ } from "openclaw/plugin-sdk/bluebubbles";
9
9
 
10
10
  export type BlueBubblesService = "imessage" | "sms" | "auto";
11
11