@openclaw/bluebubbles 2026.2.17 → 2026.2.19

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/chat.test.ts +38 -87
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/bluebubbles",
3
- "version": "2026.2.17",
3
+ "version": "2026.2.19",
4
4
  "description": "OpenClaw BlueBubbles channel plugin",
5
5
  "type": "module",
6
6
  "devDependencies": {
package/src/chat.test.ts CHANGED
@@ -13,29 +13,20 @@ installBlueBubblesFetchTestHooks({
13
13
 
14
14
  describe("chat", () => {
15
15
  describe("markBlueBubblesChatRead", () => {
16
- it("does nothing when chatGuid is empty", async () => {
17
- await markBlueBubblesChatRead("", {
18
- serverUrl: "http://localhost:1234",
19
- password: "test",
20
- });
21
- expect(mockFetch).not.toHaveBeenCalled();
22
- });
23
-
24
- it("does nothing when chatGuid is whitespace", async () => {
25
- await markBlueBubblesChatRead(" ", {
26
- serverUrl: "http://localhost:1234",
27
- password: "test",
28
- });
16
+ it("does nothing when chatGuid is empty or whitespace", async () => {
17
+ for (const chatGuid of ["", " "]) {
18
+ await markBlueBubblesChatRead(chatGuid, {
19
+ serverUrl: "http://localhost:1234",
20
+ password: "test",
21
+ });
22
+ }
29
23
  expect(mockFetch).not.toHaveBeenCalled();
30
24
  });
31
25
 
32
- it("throws when serverUrl is missing", async () => {
26
+ it("throws when required credentials are missing", async () => {
33
27
  await expect(markBlueBubblesChatRead("chat-guid", {})).rejects.toThrow(
34
28
  "serverUrl is required",
35
29
  );
36
- });
37
-
38
- it("throws when password is missing", async () => {
39
30
  await expect(
40
31
  markBlueBubblesChatRead("chat-guid", {
41
32
  serverUrl: "http://localhost:1234",
@@ -141,29 +132,20 @@ describe("chat", () => {
141
132
  });
142
133
 
143
134
  describe("sendBlueBubblesTyping", () => {
144
- it("does nothing when chatGuid is empty", async () => {
145
- await sendBlueBubblesTyping("", true, {
146
- serverUrl: "http://localhost:1234",
147
- password: "test",
148
- });
149
- expect(mockFetch).not.toHaveBeenCalled();
150
- });
151
-
152
- it("does nothing when chatGuid is whitespace", async () => {
153
- await sendBlueBubblesTyping(" ", false, {
154
- serverUrl: "http://localhost:1234",
155
- password: "test",
156
- });
135
+ it("does nothing when chatGuid is empty or whitespace", async () => {
136
+ for (const chatGuid of ["", " "]) {
137
+ await sendBlueBubblesTyping(chatGuid, true, {
138
+ serverUrl: "http://localhost:1234",
139
+ password: "test",
140
+ });
141
+ }
157
142
  expect(mockFetch).not.toHaveBeenCalled();
158
143
  });
159
144
 
160
- it("throws when serverUrl is missing", async () => {
145
+ it("throws when required credentials are missing", async () => {
161
146
  await expect(sendBlueBubblesTyping("chat-guid", true, {})).rejects.toThrow(
162
147
  "serverUrl is required",
163
148
  );
164
- });
165
-
166
- it("throws when password is missing", async () => {
167
149
  await expect(
168
150
  sendBlueBubblesTyping("chat-guid", true, {
169
151
  serverUrl: "http://localhost:1234",
@@ -171,49 +153,46 @@ describe("chat", () => {
171
153
  ).rejects.toThrow("password is required");
172
154
  });
173
155
 
174
- it("sends typing start with POST method", async () => {
175
- mockFetch.mockResolvedValueOnce({
176
- ok: true,
177
- text: () => Promise.resolve(""),
178
- });
156
+ it("does not send typing when private API is disabled", async () => {
157
+ vi.mocked(getCachedBlueBubblesPrivateApiStatus).mockReturnValueOnce(false);
179
158
 
180
159
  await sendBlueBubblesTyping("iMessage;-;+15551234567", true, {
181
160
  serverUrl: "http://localhost:1234",
182
161
  password: "test",
183
162
  });
184
163
 
185
- expect(mockFetch).toHaveBeenCalledWith(
186
- expect.stringContaining("/api/v1/chat/iMessage%3B-%3B%2B15551234567/typing"),
187
- expect.objectContaining({ method: "POST" }),
188
- );
164
+ expect(mockFetch).not.toHaveBeenCalled();
189
165
  });
190
166
 
191
- it("does not send typing when private API is disabled", async () => {
192
- vi.mocked(getCachedBlueBubblesPrivateApiStatus).mockReturnValueOnce(false);
167
+ it("uses POST for start and DELETE for stop", async () => {
168
+ mockFetch
169
+ .mockResolvedValueOnce({
170
+ ok: true,
171
+ text: () => Promise.resolve(""),
172
+ })
173
+ .mockResolvedValueOnce({
174
+ ok: true,
175
+ text: () => Promise.resolve(""),
176
+ });
193
177
 
194
178
  await sendBlueBubblesTyping("iMessage;-;+15551234567", true, {
195
179
  serverUrl: "http://localhost:1234",
196
180
  password: "test",
197
181
  });
198
-
199
- expect(mockFetch).not.toHaveBeenCalled();
200
- });
201
-
202
- it("sends typing stop with DELETE method", async () => {
203
- mockFetch.mockResolvedValueOnce({
204
- ok: true,
205
- text: () => Promise.resolve(""),
206
- });
207
-
208
182
  await sendBlueBubblesTyping("iMessage;-;+15551234567", false, {
209
183
  serverUrl: "http://localhost:1234",
210
184
  password: "test",
211
185
  });
212
186
 
213
- expect(mockFetch).toHaveBeenCalledWith(
214
- expect.stringContaining("/api/v1/chat/iMessage%3B-%3B%2B15551234567/typing"),
215
- expect.objectContaining({ method: "DELETE" }),
187
+ expect(mockFetch).toHaveBeenCalledTimes(2);
188
+ expect(mockFetch.mock.calls[0][0]).toContain(
189
+ "/api/v1/chat/iMessage%3B-%3B%2B15551234567/typing",
216
190
  );
191
+ expect(mockFetch.mock.calls[0][1].method).toBe("POST");
192
+ expect(mockFetch.mock.calls[1][0]).toContain(
193
+ "/api/v1/chat/iMessage%3B-%3B%2B15551234567/typing",
194
+ );
195
+ expect(mockFetch.mock.calls[1][1].method).toBe("DELETE");
217
196
  });
218
197
 
219
198
  it("includes password in URL query", async () => {
@@ -297,31 +276,6 @@ describe("chat", () => {
297
276
  expect(calledUrl).toContain("typing-server:8888");
298
277
  expect(calledUrl).toContain("password=typing-pass");
299
278
  });
300
-
301
- it("can start and stop typing in sequence", async () => {
302
- mockFetch
303
- .mockResolvedValueOnce({
304
- ok: true,
305
- text: () => Promise.resolve(""),
306
- })
307
- .mockResolvedValueOnce({
308
- ok: true,
309
- text: () => Promise.resolve(""),
310
- });
311
-
312
- await sendBlueBubblesTyping("chat-123", true, {
313
- serverUrl: "http://localhost:1234",
314
- password: "test",
315
- });
316
- await sendBlueBubblesTyping("chat-123", false, {
317
- serverUrl: "http://localhost:1234",
318
- password: "test",
319
- });
320
-
321
- expect(mockFetch).toHaveBeenCalledTimes(2);
322
- expect(mockFetch.mock.calls[0][1].method).toBe("POST");
323
- expect(mockFetch.mock.calls[1][1].method).toBe("DELETE");
324
- });
325
279
  });
326
280
 
327
281
  describe("setGroupIconBlueBubbles", () => {
@@ -343,13 +297,10 @@ describe("chat", () => {
343
297
  ).rejects.toThrow("image buffer");
344
298
  });
345
299
 
346
- it("throws when serverUrl is missing", async () => {
300
+ it("throws when required credentials are missing", async () => {
347
301
  await expect(
348
302
  setGroupIconBlueBubbles("chat-guid", new Uint8Array([1, 2, 3]), "icon.png", {}),
349
303
  ).rejects.toThrow("serverUrl is required");
350
- });
351
-
352
- it("throws when password is missing", async () => {
353
304
  await expect(
354
305
  setGroupIconBlueBubbles("chat-guid", new Uint8Array([1, 2, 3]), "icon.png", {
355
306
  serverUrl: "http://localhost:1234",