@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.
- package/index.ts +2 -2
- package/package.json +4 -1
- package/src/account-resolve.ts +1 -1
- package/src/accounts.ts +7 -36
- package/src/actions.test.ts +1 -1
- package/src/actions.ts +1 -1
- package/src/attachments.test.ts +1 -1
- package/src/attachments.ts +1 -1
- package/src/channel.ts +50 -70
- package/src/chat.ts +46 -39
- package/src/config-apply.ts +77 -0
- package/src/config-schema.test.ts +1 -1
- package/src/config-schema.ts +10 -8
- package/src/history.ts +1 -1
- package/src/media-send.test.ts +1 -1
- package/src/media-send.ts +1 -1
- package/src/monitor-debounce.ts +1 -1
- package/src/monitor-normalize.ts +2 -11
- package/src/monitor-processing.ts +23 -22
- package/src/monitor-shared.ts +1 -1
- package/src/monitor.test.ts +3 -3
- package/src/monitor.ts +126 -138
- package/src/monitor.webhook-auth.test.ts +77 -172
- package/src/monitor.webhook-route.test.ts +1 -1
- package/src/onboarding.secret-input.test.ts +10 -2
- package/src/onboarding.ts +29 -67
- package/src/probe.ts +1 -1
- package/src/reactions.ts +1 -1
- package/src/request-url.ts +1 -12
- package/src/runtime.ts +8 -13
- package/src/secret-input.ts +8 -14
- package/src/send.test.ts +1 -1
- package/src/send.ts +17 -22
- package/src/targets.ts +1 -1
- package/src/types.ts +2 -2
|
@@ -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
|
-
|
|
290
|
-
const
|
|
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
|
-
|
|
349
|
-
const
|
|
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(
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
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
|
-
|
|
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
|
|
517
|
-
|
|
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
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
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
|
-
|
|
774
|
-
const
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
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
|
-
|
|
823
|
-
const
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
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
|
-
|
|
15
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
-
|
|
287
|
-
next
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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";
|
package/src/request-url.ts
CHANGED
|
@@ -1,12 +1 @@
|
|
|
1
|
-
export
|
|
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
|
-
|
|
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
|
-
|
|
9
|
+
runtimeStore.clearRuntime();
|
|
12
10
|
}
|
|
13
11
|
|
|
14
12
|
export function tryGetBlueBubblesRuntime(): PluginRuntime | null {
|
|
15
|
-
return
|
|
13
|
+
return runtimeStore.tryGetRuntime();
|
|
16
14
|
}
|
|
17
15
|
|
|
18
16
|
export function getBlueBubblesRuntime(): PluginRuntime {
|
|
19
|
-
|
|
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 = (
|
|
23
|
+
const log = (runtimeStore.tryGetRuntime() as unknown as LegacyRuntimeLogShape | null)?.log;
|
|
29
24
|
if (typeof log === "function") {
|
|
30
25
|
log(formatted);
|
|
31
26
|
return;
|
package/src/secret-input.ts
CHANGED
|
@@ -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 {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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