@openclaw/bluebubbles 2026.2.15 → 2026.2.17
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/package.json +1 -1
- package/src/account-resolve.ts +29 -0
- package/src/actions.test.ts +47 -40
- package/src/actions.ts +1 -1
- package/src/attachments.test.ts +9 -29
- package/src/attachments.ts +3 -15
- package/src/chat.test.ts +8 -28
- package/src/chat.ts +3 -15
- package/src/media-send.test.ts +1 -1
- package/src/monitor-normalize.ts +1 -1
- package/src/monitor-processing.ts +204 -19
- package/src/monitor-shared.ts +1 -1
- package/src/monitor.test.ts +200 -14
- package/src/monitor.ts +14 -21
- package/src/onboarding.ts +2 -1
- package/src/reactions.ts +2 -14
- package/src/send-helpers.ts +1 -1
- package/src/send.test.ts +103 -320
- package/src/send.ts +1 -1
- package/src/targets.ts +10 -37
- package/src/test-harness.ts +50 -0
- package/src/test-mocks.ts +11 -0
package/src/monitor.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
+
import { timingSafeEqual } from "node:crypto";
|
|
1
2
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
3
|
import type { OpenClawConfig } from "openclaw/plugin-sdk";
|
|
3
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
registerWebhookTarget,
|
|
6
|
+
rejectNonPostWebhookRequest,
|
|
7
|
+
resolveWebhookTargets,
|
|
8
|
+
} from "openclaw/plugin-sdk";
|
|
4
9
|
import {
|
|
5
10
|
normalizeWebhookMessage,
|
|
6
11
|
normalizeWebhookReaction,
|
|
@@ -226,20 +231,11 @@ function removeDebouncer(target: WebhookTarget): void {
|
|
|
226
231
|
}
|
|
227
232
|
|
|
228
233
|
export function registerBlueBubblesWebhookTarget(target: WebhookTarget): () => void {
|
|
229
|
-
const
|
|
230
|
-
const normalizedTarget = { ...target, path: key };
|
|
231
|
-
const existing = webhookTargets.get(key) ?? [];
|
|
232
|
-
const next = [...existing, normalizedTarget];
|
|
233
|
-
webhookTargets.set(key, next);
|
|
234
|
+
const registered = registerWebhookTarget(webhookTargets, target);
|
|
234
235
|
return () => {
|
|
235
|
-
|
|
236
|
-
if (updated.length > 0) {
|
|
237
|
-
webhookTargets.set(key, updated);
|
|
238
|
-
} else {
|
|
239
|
-
webhookTargets.delete(key);
|
|
240
|
-
}
|
|
236
|
+
registered.unregister();
|
|
241
237
|
// Clean up debouncer when target is unregistered
|
|
242
|
-
removeDebouncer(
|
|
238
|
+
removeDebouncer(registered.target);
|
|
243
239
|
};
|
|
244
240
|
}
|
|
245
241
|
|
|
@@ -387,17 +383,14 @@ export async function handleBlueBubblesWebhookRequest(
|
|
|
387
383
|
req: IncomingMessage,
|
|
388
384
|
res: ServerResponse,
|
|
389
385
|
): Promise<boolean> {
|
|
390
|
-
const
|
|
391
|
-
|
|
392
|
-
const targets = webhookTargets.get(path);
|
|
393
|
-
if (!targets || targets.length === 0) {
|
|
386
|
+
const resolved = resolveWebhookTargets(req, webhookTargets);
|
|
387
|
+
if (!resolved) {
|
|
394
388
|
return false;
|
|
395
389
|
}
|
|
390
|
+
const { path, targets } = resolved;
|
|
391
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
396
392
|
|
|
397
|
-
if (req
|
|
398
|
-
res.statusCode = 405;
|
|
399
|
-
res.setHeader("Allow", "POST");
|
|
400
|
-
res.end("Method Not Allowed");
|
|
393
|
+
if (rejectNonPostWebhookRequest(req, res)) {
|
|
401
394
|
return true;
|
|
402
395
|
}
|
|
403
396
|
|
package/src/onboarding.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
DEFAULT_ACCOUNT_ID,
|
|
10
10
|
addWildcardAllowFrom,
|
|
11
11
|
formatDocsLink,
|
|
12
|
+
mergeAllowFromEntries,
|
|
12
13
|
normalizeAccountId,
|
|
13
14
|
promptAccountId,
|
|
14
15
|
} from "openclaw/plugin-sdk";
|
|
@@ -127,7 +128,7 @@ async function promptBlueBubblesAllowFrom(params: {
|
|
|
127
128
|
},
|
|
128
129
|
});
|
|
129
130
|
const parts = parseBlueBubblesAllowFromInput(String(entry));
|
|
130
|
-
const unique =
|
|
131
|
+
const unique = mergeAllowFromEntries(undefined, parts);
|
|
131
132
|
return setBlueBubblesAllowFrom(params.cfg, accountId, unique);
|
|
132
133
|
}
|
|
133
134
|
|
package/src/reactions.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { OpenClawConfig } from "openclaw/plugin-sdk";
|
|
2
|
-
import {
|
|
2
|
+
import { resolveBlueBubblesServerAccount } from "./account-resolve.js";
|
|
3
3
|
import { getCachedBlueBubblesPrivateApiStatus } from "./probe.js";
|
|
4
4
|
import { blueBubblesFetchWithTimeout, buildBlueBubblesApiUrl } from "./types.js";
|
|
5
5
|
|
|
@@ -112,19 +112,7 @@ const REACTION_EMOJIS = new Map<string, string>([
|
|
|
112
112
|
]);
|
|
113
113
|
|
|
114
114
|
function resolveAccount(params: BlueBubblesReactionOpts) {
|
|
115
|
-
|
|
116
|
-
cfg: params.cfg ?? {},
|
|
117
|
-
accountId: params.accountId,
|
|
118
|
-
});
|
|
119
|
-
const baseUrl = params.serverUrl?.trim() || account.config.serverUrl?.trim();
|
|
120
|
-
const password = params.password?.trim() || account.config.password?.trim();
|
|
121
|
-
if (!baseUrl) {
|
|
122
|
-
throw new Error("BlueBubbles serverUrl is required");
|
|
123
|
-
}
|
|
124
|
-
if (!password) {
|
|
125
|
-
throw new Error("BlueBubbles password is required");
|
|
126
|
-
}
|
|
127
|
-
return { baseUrl, password, accountId: account.accountId };
|
|
115
|
+
return resolveBlueBubblesServerAccount(params);
|
|
128
116
|
}
|
|
129
117
|
|
|
130
118
|
export function normalizeBlueBubblesReactionInput(emoji: string, remove?: boolean): string {
|
package/src/send-helpers.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { BlueBubblesSendTarget } from "./types.js";
|
|
2
1
|
import { normalizeBlueBubblesHandle, parseBlueBubblesTarget } from "./targets.js";
|
|
2
|
+
import type { BlueBubblesSendTarget } from "./types.js";
|
|
3
3
|
|
|
4
4
|
export function resolveBlueBubblesSendTarget(raw: string): BlueBubblesSendTarget {
|
|
5
5
|
const parsed = parseBlueBubblesTarget(raw);
|
package/src/send.test.ts
CHANGED
|
@@ -1,39 +1,62 @@
|
|
|
1
|
-
import { describe, expect, it, vi
|
|
2
|
-
import
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import "./test-mocks.js";
|
|
3
3
|
import { getCachedBlueBubblesPrivateApiStatus } from "./probe.js";
|
|
4
4
|
import { sendMessageBlueBubbles, resolveChatGuidForTarget } from "./send.js";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
resolveBlueBubblesAccount: vi.fn(({ cfg, accountId }) => {
|
|
8
|
-
const config = cfg?.channels?.bluebubbles ?? {};
|
|
9
|
-
return {
|
|
10
|
-
accountId: accountId ?? "default",
|
|
11
|
-
enabled: config.enabled !== false,
|
|
12
|
-
configured: Boolean(config.serverUrl && config.password),
|
|
13
|
-
config,
|
|
14
|
-
};
|
|
15
|
-
}),
|
|
16
|
-
}));
|
|
17
|
-
|
|
18
|
-
vi.mock("./probe.js", () => ({
|
|
19
|
-
getCachedBlueBubblesPrivateApiStatus: vi.fn().mockReturnValue(null),
|
|
20
|
-
}));
|
|
5
|
+
import { installBlueBubblesFetchTestHooks } from "./test-harness.js";
|
|
6
|
+
import type { BlueBubblesSendTarget } from "./types.js";
|
|
21
7
|
|
|
22
8
|
const mockFetch = vi.fn();
|
|
23
9
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
10
|
+
installBlueBubblesFetchTestHooks({
|
|
11
|
+
mockFetch,
|
|
12
|
+
privateApiStatusMock: vi.mocked(getCachedBlueBubblesPrivateApiStatus),
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
function mockResolvedHandleTarget(
|
|
16
|
+
guid: string = "iMessage;-;+15551234567",
|
|
17
|
+
address: string = "+15551234567",
|
|
18
|
+
) {
|
|
19
|
+
mockFetch.mockResolvedValueOnce({
|
|
20
|
+
ok: true,
|
|
21
|
+
json: () =>
|
|
22
|
+
Promise.resolve({
|
|
23
|
+
data: [
|
|
24
|
+
{
|
|
25
|
+
guid,
|
|
26
|
+
participants: [{ address }],
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
}),
|
|
30
30
|
});
|
|
31
|
+
}
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
function mockSendResponse(body: unknown) {
|
|
34
|
+
mockFetch.mockResolvedValueOnce({
|
|
35
|
+
ok: true,
|
|
36
|
+
text: () => Promise.resolve(JSON.stringify(body)),
|
|
34
37
|
});
|
|
38
|
+
}
|
|
35
39
|
|
|
40
|
+
describe("send", () => {
|
|
36
41
|
describe("resolveChatGuidForTarget", () => {
|
|
42
|
+
const resolveHandleTargetGuid = async (data: Array<Record<string, unknown>>) => {
|
|
43
|
+
mockFetch.mockResolvedValueOnce({
|
|
44
|
+
ok: true,
|
|
45
|
+
json: () => Promise.resolve({ data }),
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const target: BlueBubblesSendTarget = {
|
|
49
|
+
kind: "handle",
|
|
50
|
+
address: "+15551234567",
|
|
51
|
+
service: "imessage",
|
|
52
|
+
};
|
|
53
|
+
return await resolveChatGuidForTarget({
|
|
54
|
+
baseUrl: "http://localhost:1234",
|
|
55
|
+
password: "test",
|
|
56
|
+
target,
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
|
|
37
60
|
it("returns chatGuid directly for chat_guid target", async () => {
|
|
38
61
|
const target: BlueBubblesSendTarget = {
|
|
39
62
|
kind: "chat_guid",
|
|
@@ -130,65 +153,31 @@ describe("send", () => {
|
|
|
130
153
|
});
|
|
131
154
|
|
|
132
155
|
it("resolves handle target by matching participant", async () => {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
guid: "iMessage;-;+15551234567",
|
|
144
|
-
participants: [{ address: "+15551234567" }],
|
|
145
|
-
},
|
|
146
|
-
],
|
|
147
|
-
}),
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
const target: BlueBubblesSendTarget = {
|
|
151
|
-
kind: "handle",
|
|
152
|
-
address: "+15551234567",
|
|
153
|
-
service: "imessage",
|
|
154
|
-
};
|
|
155
|
-
const result = await resolveChatGuidForTarget({
|
|
156
|
-
baseUrl: "http://localhost:1234",
|
|
157
|
-
password: "test",
|
|
158
|
-
target,
|
|
159
|
-
});
|
|
156
|
+
const result = await resolveHandleTargetGuid([
|
|
157
|
+
{
|
|
158
|
+
guid: "iMessage;-;+15559999999",
|
|
159
|
+
participants: [{ address: "+15559999999" }],
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
guid: "iMessage;-;+15551234567",
|
|
163
|
+
participants: [{ address: "+15551234567" }],
|
|
164
|
+
},
|
|
165
|
+
]);
|
|
160
166
|
|
|
161
167
|
expect(result).toBe("iMessage;-;+15551234567");
|
|
162
168
|
});
|
|
163
169
|
|
|
164
170
|
it("prefers direct chat guid when handle also appears in a group chat", async () => {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
guid: "iMessage;-;+15551234567",
|
|
176
|
-
participants: [{ address: "+15551234567" }],
|
|
177
|
-
},
|
|
178
|
-
],
|
|
179
|
-
}),
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
const target: BlueBubblesSendTarget = {
|
|
183
|
-
kind: "handle",
|
|
184
|
-
address: "+15551234567",
|
|
185
|
-
service: "imessage",
|
|
186
|
-
};
|
|
187
|
-
const result = await resolveChatGuidForTarget({
|
|
188
|
-
baseUrl: "http://localhost:1234",
|
|
189
|
-
password: "test",
|
|
190
|
-
target,
|
|
191
|
-
});
|
|
171
|
+
const result = await resolveHandleTargetGuid([
|
|
172
|
+
{
|
|
173
|
+
guid: "iMessage;+;group-123",
|
|
174
|
+
participants: [{ address: "+15551234567" }, { address: "+15550001111" }],
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
guid: "iMessage;-;+15551234567",
|
|
178
|
+
participants: [{ address: "+15551234567" }],
|
|
179
|
+
},
|
|
180
|
+
]);
|
|
192
181
|
|
|
193
182
|
expect(result).toBe("iMessage;-;+15551234567");
|
|
194
183
|
});
|
|
@@ -416,28 +405,8 @@ describe("send", () => {
|
|
|
416
405
|
});
|
|
417
406
|
|
|
418
407
|
it("sends message successfully", async () => {
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
ok: true,
|
|
422
|
-
json: () =>
|
|
423
|
-
Promise.resolve({
|
|
424
|
-
data: [
|
|
425
|
-
{
|
|
426
|
-
guid: "iMessage;-;+15551234567",
|
|
427
|
-
participants: [{ address: "+15551234567" }],
|
|
428
|
-
},
|
|
429
|
-
],
|
|
430
|
-
}),
|
|
431
|
-
})
|
|
432
|
-
.mockResolvedValueOnce({
|
|
433
|
-
ok: true,
|
|
434
|
-
text: () =>
|
|
435
|
-
Promise.resolve(
|
|
436
|
-
JSON.stringify({
|
|
437
|
-
data: { guid: "msg-uuid-123" },
|
|
438
|
-
}),
|
|
439
|
-
),
|
|
440
|
-
});
|
|
408
|
+
mockResolvedHandleTarget();
|
|
409
|
+
mockSendResponse({ data: { guid: "msg-uuid-123" } });
|
|
441
410
|
|
|
442
411
|
const result = await sendMessageBlueBubbles("+15551234567", "Hello world!", {
|
|
443
412
|
serverUrl: "http://localhost:1234",
|
|
@@ -456,28 +425,8 @@ describe("send", () => {
|
|
|
456
425
|
});
|
|
457
426
|
|
|
458
427
|
it("strips markdown formatting from outbound messages", async () => {
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
ok: true,
|
|
462
|
-
json: () =>
|
|
463
|
-
Promise.resolve({
|
|
464
|
-
data: [
|
|
465
|
-
{
|
|
466
|
-
guid: "iMessage;-;+15551234567",
|
|
467
|
-
participants: [{ address: "+15551234567" }],
|
|
468
|
-
},
|
|
469
|
-
],
|
|
470
|
-
}),
|
|
471
|
-
})
|
|
472
|
-
.mockResolvedValueOnce({
|
|
473
|
-
ok: true,
|
|
474
|
-
text: () =>
|
|
475
|
-
Promise.resolve(
|
|
476
|
-
JSON.stringify({
|
|
477
|
-
data: { guid: "msg-uuid-stripped" },
|
|
478
|
-
}),
|
|
479
|
-
),
|
|
480
|
-
});
|
|
428
|
+
mockResolvedHandleTarget();
|
|
429
|
+
mockSendResponse({ data: { guid: "msg-uuid-stripped" } });
|
|
481
430
|
|
|
482
431
|
const result = await sendMessageBlueBubbles(
|
|
483
432
|
"+15551234567",
|
|
@@ -578,28 +527,8 @@ describe("send", () => {
|
|
|
578
527
|
});
|
|
579
528
|
|
|
580
529
|
it("uses private-api when reply metadata is present", async () => {
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
ok: true,
|
|
584
|
-
json: () =>
|
|
585
|
-
Promise.resolve({
|
|
586
|
-
data: [
|
|
587
|
-
{
|
|
588
|
-
guid: "iMessage;-;+15551234567",
|
|
589
|
-
participants: [{ address: "+15551234567" }],
|
|
590
|
-
},
|
|
591
|
-
],
|
|
592
|
-
}),
|
|
593
|
-
})
|
|
594
|
-
.mockResolvedValueOnce({
|
|
595
|
-
ok: true,
|
|
596
|
-
text: () =>
|
|
597
|
-
Promise.resolve(
|
|
598
|
-
JSON.stringify({
|
|
599
|
-
data: { guid: "msg-uuid-124" },
|
|
600
|
-
}),
|
|
601
|
-
),
|
|
602
|
-
});
|
|
530
|
+
mockResolvedHandleTarget();
|
|
531
|
+
mockSendResponse({ data: { guid: "msg-uuid-124" } });
|
|
603
532
|
|
|
604
533
|
const result = await sendMessageBlueBubbles("+15551234567", "Replying", {
|
|
605
534
|
serverUrl: "http://localhost:1234",
|
|
@@ -620,28 +549,8 @@ describe("send", () => {
|
|
|
620
549
|
|
|
621
550
|
it("downgrades threaded reply to plain send when private API is disabled", async () => {
|
|
622
551
|
vi.mocked(getCachedBlueBubblesPrivateApiStatus).mockReturnValueOnce(false);
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
ok: true,
|
|
626
|
-
json: () =>
|
|
627
|
-
Promise.resolve({
|
|
628
|
-
data: [
|
|
629
|
-
{
|
|
630
|
-
guid: "iMessage;-;+15551234567",
|
|
631
|
-
participants: [{ address: "+15551234567" }],
|
|
632
|
-
},
|
|
633
|
-
],
|
|
634
|
-
}),
|
|
635
|
-
})
|
|
636
|
-
.mockResolvedValueOnce({
|
|
637
|
-
ok: true,
|
|
638
|
-
text: () =>
|
|
639
|
-
Promise.resolve(
|
|
640
|
-
JSON.stringify({
|
|
641
|
-
data: { guid: "msg-uuid-plain" },
|
|
642
|
-
}),
|
|
643
|
-
),
|
|
644
|
-
});
|
|
552
|
+
mockResolvedHandleTarget();
|
|
553
|
+
mockSendResponse({ data: { guid: "msg-uuid-plain" } });
|
|
645
554
|
|
|
646
555
|
const result = await sendMessageBlueBubbles("+15551234567", "Reply fallback", {
|
|
647
556
|
serverUrl: "http://localhost:1234",
|
|
@@ -659,28 +568,8 @@ describe("send", () => {
|
|
|
659
568
|
});
|
|
660
569
|
|
|
661
570
|
it("normalizes effect names and uses private-api for effects", async () => {
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
ok: true,
|
|
665
|
-
json: () =>
|
|
666
|
-
Promise.resolve({
|
|
667
|
-
data: [
|
|
668
|
-
{
|
|
669
|
-
guid: "iMessage;-;+15551234567",
|
|
670
|
-
participants: [{ address: "+15551234567" }],
|
|
671
|
-
},
|
|
672
|
-
],
|
|
673
|
-
}),
|
|
674
|
-
})
|
|
675
|
-
.mockResolvedValueOnce({
|
|
676
|
-
ok: true,
|
|
677
|
-
text: () =>
|
|
678
|
-
Promise.resolve(
|
|
679
|
-
JSON.stringify({
|
|
680
|
-
data: { guid: "msg-uuid-125" },
|
|
681
|
-
}),
|
|
682
|
-
),
|
|
683
|
-
});
|
|
571
|
+
mockResolvedHandleTarget();
|
|
572
|
+
mockSendResponse({ data: { guid: "msg-uuid-125" } });
|
|
684
573
|
|
|
685
574
|
const result = await sendMessageBlueBubbles("+15551234567", "Hello", {
|
|
686
575
|
serverUrl: "http://localhost:1234",
|
|
@@ -722,24 +611,12 @@ describe("send", () => {
|
|
|
722
611
|
});
|
|
723
612
|
|
|
724
613
|
it("handles send failure", async () => {
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
{
|
|
732
|
-
guid: "iMessage;-;+15551234567",
|
|
733
|
-
participants: [{ address: "+15551234567" }],
|
|
734
|
-
},
|
|
735
|
-
],
|
|
736
|
-
}),
|
|
737
|
-
})
|
|
738
|
-
.mockResolvedValueOnce({
|
|
739
|
-
ok: false,
|
|
740
|
-
status: 500,
|
|
741
|
-
text: () => Promise.resolve("Internal server error"),
|
|
742
|
-
});
|
|
614
|
+
mockResolvedHandleTarget();
|
|
615
|
+
mockFetch.mockResolvedValueOnce({
|
|
616
|
+
ok: false,
|
|
617
|
+
status: 500,
|
|
618
|
+
text: () => Promise.resolve("Internal server error"),
|
|
619
|
+
});
|
|
743
620
|
|
|
744
621
|
await expect(
|
|
745
622
|
sendMessageBlueBubbles("+15551234567", "Hello", {
|
|
@@ -750,23 +627,11 @@ describe("send", () => {
|
|
|
750
627
|
});
|
|
751
628
|
|
|
752
629
|
it("handles empty response body", async () => {
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
data: [
|
|
759
|
-
{
|
|
760
|
-
guid: "iMessage;-;+15551234567",
|
|
761
|
-
participants: [{ address: "+15551234567" }],
|
|
762
|
-
},
|
|
763
|
-
],
|
|
764
|
-
}),
|
|
765
|
-
})
|
|
766
|
-
.mockResolvedValueOnce({
|
|
767
|
-
ok: true,
|
|
768
|
-
text: () => Promise.resolve(""),
|
|
769
|
-
});
|
|
630
|
+
mockResolvedHandleTarget();
|
|
631
|
+
mockFetch.mockResolvedValueOnce({
|
|
632
|
+
ok: true,
|
|
633
|
+
text: () => Promise.resolve(""),
|
|
634
|
+
});
|
|
770
635
|
|
|
771
636
|
const result = await sendMessageBlueBubbles("+15551234567", "Hello", {
|
|
772
637
|
serverUrl: "http://localhost:1234",
|
|
@@ -777,23 +642,11 @@ describe("send", () => {
|
|
|
777
642
|
});
|
|
778
643
|
|
|
779
644
|
it("handles invalid JSON response body", async () => {
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
data: [
|
|
786
|
-
{
|
|
787
|
-
guid: "iMessage;-;+15551234567",
|
|
788
|
-
participants: [{ address: "+15551234567" }],
|
|
789
|
-
},
|
|
790
|
-
],
|
|
791
|
-
}),
|
|
792
|
-
})
|
|
793
|
-
.mockResolvedValueOnce({
|
|
794
|
-
ok: true,
|
|
795
|
-
text: () => Promise.resolve("not valid json"),
|
|
796
|
-
});
|
|
645
|
+
mockResolvedHandleTarget();
|
|
646
|
+
mockFetch.mockResolvedValueOnce({
|
|
647
|
+
ok: true,
|
|
648
|
+
text: () => Promise.resolve("not valid json"),
|
|
649
|
+
});
|
|
797
650
|
|
|
798
651
|
const result = await sendMessageBlueBubbles("+15551234567", "Hello", {
|
|
799
652
|
serverUrl: "http://localhost:1234",
|
|
@@ -804,28 +657,8 @@ describe("send", () => {
|
|
|
804
657
|
});
|
|
805
658
|
|
|
806
659
|
it("extracts messageId from various response formats", async () => {
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
ok: true,
|
|
810
|
-
json: () =>
|
|
811
|
-
Promise.resolve({
|
|
812
|
-
data: [
|
|
813
|
-
{
|
|
814
|
-
guid: "iMessage;-;+15551234567",
|
|
815
|
-
participants: [{ address: "+15551234567" }],
|
|
816
|
-
},
|
|
817
|
-
],
|
|
818
|
-
}),
|
|
819
|
-
})
|
|
820
|
-
.mockResolvedValueOnce({
|
|
821
|
-
ok: true,
|
|
822
|
-
text: () =>
|
|
823
|
-
Promise.resolve(
|
|
824
|
-
JSON.stringify({
|
|
825
|
-
id: "numeric-id-456",
|
|
826
|
-
}),
|
|
827
|
-
),
|
|
828
|
-
});
|
|
660
|
+
mockResolvedHandleTarget();
|
|
661
|
+
mockSendResponse({ id: "numeric-id-456" });
|
|
829
662
|
|
|
830
663
|
const result = await sendMessageBlueBubbles("+15551234567", "Hello", {
|
|
831
664
|
serverUrl: "http://localhost:1234",
|
|
@@ -836,28 +669,8 @@ describe("send", () => {
|
|
|
836
669
|
});
|
|
837
670
|
|
|
838
671
|
it("extracts messageGuid from response payload", async () => {
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
ok: true,
|
|
842
|
-
json: () =>
|
|
843
|
-
Promise.resolve({
|
|
844
|
-
data: [
|
|
845
|
-
{
|
|
846
|
-
guid: "iMessage;-;+15551234567",
|
|
847
|
-
participants: [{ address: "+15551234567" }],
|
|
848
|
-
},
|
|
849
|
-
],
|
|
850
|
-
}),
|
|
851
|
-
})
|
|
852
|
-
.mockResolvedValueOnce({
|
|
853
|
-
ok: true,
|
|
854
|
-
text: () =>
|
|
855
|
-
Promise.resolve(
|
|
856
|
-
JSON.stringify({
|
|
857
|
-
data: { messageGuid: "msg-guid-789" },
|
|
858
|
-
}),
|
|
859
|
-
),
|
|
860
|
-
});
|
|
672
|
+
mockResolvedHandleTarget();
|
|
673
|
+
mockSendResponse({ data: { messageGuid: "msg-guid-789" } });
|
|
861
674
|
|
|
862
675
|
const result = await sendMessageBlueBubbles("+15551234567", "Hello", {
|
|
863
676
|
serverUrl: "http://localhost:1234",
|
|
@@ -868,23 +681,8 @@ describe("send", () => {
|
|
|
868
681
|
});
|
|
869
682
|
|
|
870
683
|
it("resolves credentials from config", async () => {
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
ok: true,
|
|
874
|
-
json: () =>
|
|
875
|
-
Promise.resolve({
|
|
876
|
-
data: [
|
|
877
|
-
{
|
|
878
|
-
guid: "iMessage;-;+15551234567",
|
|
879
|
-
participants: [{ address: "+15551234567" }],
|
|
880
|
-
},
|
|
881
|
-
],
|
|
882
|
-
}),
|
|
883
|
-
})
|
|
884
|
-
.mockResolvedValueOnce({
|
|
885
|
-
ok: true,
|
|
886
|
-
text: () => Promise.resolve(JSON.stringify({ data: { guid: "msg-123" } })),
|
|
887
|
-
});
|
|
684
|
+
mockResolvedHandleTarget();
|
|
685
|
+
mockSendResponse({ data: { guid: "msg-123" } });
|
|
888
686
|
|
|
889
687
|
const result = await sendMessageBlueBubbles("+15551234567", "Hello", {
|
|
890
688
|
cfg: {
|
|
@@ -903,23 +701,8 @@ describe("send", () => {
|
|
|
903
701
|
});
|
|
904
702
|
|
|
905
703
|
it("includes tempGuid in request payload", async () => {
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
ok: true,
|
|
909
|
-
json: () =>
|
|
910
|
-
Promise.resolve({
|
|
911
|
-
data: [
|
|
912
|
-
{
|
|
913
|
-
guid: "iMessage;-;+15551234567",
|
|
914
|
-
participants: [{ address: "+15551234567" }],
|
|
915
|
-
},
|
|
916
|
-
],
|
|
917
|
-
}),
|
|
918
|
-
})
|
|
919
|
-
.mockResolvedValueOnce({
|
|
920
|
-
ok: true,
|
|
921
|
-
text: () => Promise.resolve(JSON.stringify({ data: { guid: "msg" } })),
|
|
922
|
-
});
|
|
704
|
+
mockResolvedHandleTarget();
|
|
705
|
+
mockSendResponse({ data: { guid: "msg" } });
|
|
923
706
|
|
|
924
707
|
await sendMessageBlueBubbles("+15551234567", "Hello", {
|
|
925
708
|
serverUrl: "http://localhost:1234",
|
package/src/send.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { OpenClawConfig } from "openclaw/plugin-sdk";
|
|
2
1
|
import crypto from "node:crypto";
|
|
2
|
+
import type { OpenClawConfig } from "openclaw/plugin-sdk";
|
|
3
3
|
import { stripMarkdown } from "openclaw/plugin-sdk";
|
|
4
4
|
import { resolveBlueBubblesAccount } from "./accounts.js";
|
|
5
5
|
import { getCachedBlueBubblesPrivateApiStatus } from "./probe.js";
|