@openclaw/bluebubbles 2026.1.29 → 2026.2.2

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/src/targets.ts CHANGED
@@ -20,20 +20,27 @@ const SERVICE_PREFIXES: Array<{ prefix: string; service: BlueBubblesService }> =
20
20
  { prefix: "sms:", service: "sms" },
21
21
  { prefix: "auto:", service: "auto" },
22
22
  ];
23
- const CHAT_IDENTIFIER_UUID_RE =
24
- /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
23
+ const CHAT_IDENTIFIER_UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
25
24
  const CHAT_IDENTIFIER_HEX_RE = /^[0-9a-f]{24,64}$/i;
26
25
 
27
26
  function parseRawChatGuid(value: string): string | null {
28
27
  const trimmed = value.trim();
29
- if (!trimmed) return null;
28
+ if (!trimmed) {
29
+ return null;
30
+ }
30
31
  const parts = trimmed.split(";");
31
- if (parts.length !== 3) return null;
32
+ if (parts.length !== 3) {
33
+ return null;
34
+ }
32
35
  const service = parts[0]?.trim();
33
36
  const separator = parts[1]?.trim();
34
37
  const identifier = parts[2]?.trim();
35
- if (!service || !identifier) return null;
36
- if (separator !== "+" && separator !== "-") return null;
38
+ if (!service || !identifier) {
39
+ return null;
40
+ }
41
+ if (separator !== "+" && separator !== "-") {
42
+ return null;
43
+ }
37
44
  return `${service};${separator};${identifier}`;
38
45
  }
39
46
 
@@ -43,26 +50,44 @@ function stripPrefix(value: string, prefix: string): string {
43
50
 
44
51
  function stripBlueBubblesPrefix(value: string): string {
45
52
  const trimmed = value.trim();
46
- if (!trimmed) return "";
47
- if (!trimmed.toLowerCase().startsWith("bluebubbles:")) return trimmed;
53
+ if (!trimmed) {
54
+ return "";
55
+ }
56
+ if (!trimmed.toLowerCase().startsWith("bluebubbles:")) {
57
+ return trimmed;
58
+ }
48
59
  return trimmed.slice("bluebubbles:".length).trim();
49
60
  }
50
61
 
51
62
  function looksLikeRawChatIdentifier(value: string): boolean {
52
63
  const trimmed = value.trim();
53
- if (!trimmed) return false;
54
- if (/^chat\d+$/i.test(trimmed)) return true;
64
+ if (!trimmed) {
65
+ return false;
66
+ }
67
+ if (/^chat\d+$/i.test(trimmed)) {
68
+ return true;
69
+ }
55
70
  return CHAT_IDENTIFIER_UUID_RE.test(trimmed) || CHAT_IDENTIFIER_HEX_RE.test(trimmed);
56
71
  }
57
72
 
58
73
  export function normalizeBlueBubblesHandle(raw: string): string {
59
74
  const trimmed = raw.trim();
60
- if (!trimmed) return "";
75
+ if (!trimmed) {
76
+ return "";
77
+ }
61
78
  const lowered = trimmed.toLowerCase();
62
- if (lowered.startsWith("imessage:")) return normalizeBlueBubblesHandle(trimmed.slice(9));
63
- if (lowered.startsWith("sms:")) return normalizeBlueBubblesHandle(trimmed.slice(4));
64
- if (lowered.startsWith("auto:")) return normalizeBlueBubblesHandle(trimmed.slice(5));
65
- if (trimmed.includes("@")) return trimmed.toLowerCase();
79
+ if (lowered.startsWith("imessage:")) {
80
+ return normalizeBlueBubblesHandle(trimmed.slice(9));
81
+ }
82
+ if (lowered.startsWith("sms:")) {
83
+ return normalizeBlueBubblesHandle(trimmed.slice(4));
84
+ }
85
+ if (lowered.startsWith("auto:")) {
86
+ return normalizeBlueBubblesHandle(trimmed.slice(5));
87
+ }
88
+ if (trimmed.includes("@")) {
89
+ return trimmed.toLowerCase();
90
+ }
66
91
  return trimmed.replace(/\s+/g, "");
67
92
  }
68
93
 
@@ -76,30 +101,44 @@ export function extractHandleFromChatGuid(chatGuid: string): string | null {
76
101
  // DM format: service;-;handle (3 parts, middle is "-")
77
102
  if (parts.length === 3 && parts[1] === "-") {
78
103
  const handle = parts[2]?.trim();
79
- if (handle) return normalizeBlueBubblesHandle(handle);
104
+ if (handle) {
105
+ return normalizeBlueBubblesHandle(handle);
106
+ }
80
107
  }
81
108
  return null;
82
109
  }
83
110
 
84
111
  export function normalizeBlueBubblesMessagingTarget(raw: string): string | undefined {
85
112
  let trimmed = raw.trim();
86
- if (!trimmed) return undefined;
113
+ if (!trimmed) {
114
+ return undefined;
115
+ }
87
116
  trimmed = stripBlueBubblesPrefix(trimmed);
88
- if (!trimmed) return undefined;
117
+ if (!trimmed) {
118
+ return undefined;
119
+ }
89
120
  try {
90
121
  const parsed = parseBlueBubblesTarget(trimmed);
91
- if (parsed.kind === "chat_id") return `chat_id:${parsed.chatId}`;
122
+ if (parsed.kind === "chat_id") {
123
+ return `chat_id:${parsed.chatId}`;
124
+ }
92
125
  if (parsed.kind === "chat_guid") {
93
126
  // For DM chat_guids, normalize to just the handle for easier comparison.
94
127
  // This allows "chat_guid:iMessage;-;+1234567890" to match "+1234567890".
95
128
  const handle = extractHandleFromChatGuid(parsed.chatGuid);
96
- if (handle) return handle;
129
+ if (handle) {
130
+ return handle;
131
+ }
97
132
  // For group chats or unrecognized formats, keep the full chat_guid
98
133
  return `chat_guid:${parsed.chatGuid}`;
99
134
  }
100
- if (parsed.kind === "chat_identifier") return `chat_identifier:${parsed.chatIdentifier}`;
135
+ if (parsed.kind === "chat_identifier") {
136
+ return `chat_identifier:${parsed.chatIdentifier}`;
137
+ }
101
138
  const handle = normalizeBlueBubblesHandle(parsed.to);
102
- if (!handle) return undefined;
139
+ if (!handle) {
140
+ return undefined;
141
+ }
103
142
  return parsed.service === "auto" ? handle : `${parsed.service}:${handle}`;
104
143
  } catch {
105
144
  return trimmed;
@@ -108,12 +147,20 @@ export function normalizeBlueBubblesMessagingTarget(raw: string): string | undef
108
147
 
109
148
  export function looksLikeBlueBubblesTargetId(raw: string, normalized?: string): boolean {
110
149
  const trimmed = raw.trim();
111
- if (!trimmed) return false;
150
+ if (!trimmed) {
151
+ return false;
152
+ }
112
153
  const candidate = stripBlueBubblesPrefix(trimmed);
113
- if (!candidate) return false;
114
- if (parseRawChatGuid(candidate)) return true;
154
+ if (!candidate) {
155
+ return false;
156
+ }
157
+ if (parseRawChatGuid(candidate)) {
158
+ return true;
159
+ }
115
160
  const lowered = candidate.toLowerCase();
116
- if (/^(imessage|sms|auto):/.test(lowered)) return true;
161
+ if (/^(imessage|sms|auto):/.test(lowered)) {
162
+ return true;
163
+ }
117
164
  if (
118
165
  /^(chat_id|chatid|chat|chat_guid|chatguid|guid|chat_identifier|chatidentifier|chatident|group):/.test(
119
166
  lowered,
@@ -122,14 +169,24 @@ export function looksLikeBlueBubblesTargetId(raw: string, normalized?: string):
122
169
  return true;
123
170
  }
124
171
  // Recognize chat<digits> patterns (e.g., "chat660250192681427962") as chat IDs
125
- if (/^chat\d+$/i.test(candidate)) return true;
126
- if (looksLikeRawChatIdentifier(candidate)) return true;
127
- if (candidate.includes("@")) return true;
172
+ if (/^chat\d+$/i.test(candidate)) {
173
+ return true;
174
+ }
175
+ if (looksLikeRawChatIdentifier(candidate)) {
176
+ return true;
177
+ }
178
+ if (candidate.includes("@")) {
179
+ return true;
180
+ }
128
181
  const digitsOnly = candidate.replace(/[\s().-]/g, "");
129
- if (/^\+?\d{3,}$/.test(digitsOnly)) return true;
182
+ if (/^\+?\d{3,}$/.test(digitsOnly)) {
183
+ return true;
184
+ }
130
185
  if (normalized) {
131
186
  const normalizedTrimmed = normalized.trim();
132
- if (!normalizedTrimmed) return false;
187
+ if (!normalizedTrimmed) {
188
+ return false;
189
+ }
133
190
  const normalizedLower = normalizedTrimmed.toLowerCase();
134
191
  if (
135
192
  /^(imessage|sms|auto):/.test(normalizedLower) ||
@@ -143,13 +200,17 @@ export function looksLikeBlueBubblesTargetId(raw: string, normalized?: string):
143
200
 
144
201
  export function parseBlueBubblesTarget(raw: string): BlueBubblesTarget {
145
202
  const trimmed = stripBlueBubblesPrefix(raw);
146
- if (!trimmed) throw new Error("BlueBubbles target is required");
203
+ if (!trimmed) {
204
+ throw new Error("BlueBubbles target is required");
205
+ }
147
206
  const lower = trimmed.toLowerCase();
148
207
 
149
208
  for (const { prefix, service } of SERVICE_PREFIXES) {
150
209
  if (lower.startsWith(prefix)) {
151
210
  const remainder = stripPrefix(trimmed, prefix);
152
- if (!remainder) throw new Error(`${prefix} target is required`);
211
+ if (!remainder) {
212
+ throw new Error(`${prefix} target is required`);
213
+ }
153
214
  const remainderLower = remainder.toLowerCase();
154
215
  const isChatTarget =
155
216
  CHAT_ID_PREFIXES.some((p) => remainderLower.startsWith(p)) ||
@@ -177,7 +238,9 @@ export function parseBlueBubblesTarget(raw: string): BlueBubblesTarget {
177
238
  for (const prefix of CHAT_GUID_PREFIXES) {
178
239
  if (lower.startsWith(prefix)) {
179
240
  const value = stripPrefix(trimmed, prefix);
180
- if (!value) throw new Error("chat_guid is required");
241
+ if (!value) {
242
+ throw new Error("chat_guid is required");
243
+ }
181
244
  return { kind: "chat_guid", chatGuid: value };
182
245
  }
183
246
  }
@@ -185,7 +248,9 @@ export function parseBlueBubblesTarget(raw: string): BlueBubblesTarget {
185
248
  for (const prefix of CHAT_IDENTIFIER_PREFIXES) {
186
249
  if (lower.startsWith(prefix)) {
187
250
  const value = stripPrefix(trimmed, prefix);
188
- if (!value) throw new Error("chat_identifier is required");
251
+ if (!value) {
252
+ throw new Error("chat_identifier is required");
253
+ }
189
254
  return { kind: "chat_identifier", chatIdentifier: value };
190
255
  }
191
256
  }
@@ -196,7 +261,9 @@ export function parseBlueBubblesTarget(raw: string): BlueBubblesTarget {
196
261
  if (Number.isFinite(chatId)) {
197
262
  return { kind: "chat_id", chatId };
198
263
  }
199
- if (!value) throw new Error("group target is required");
264
+ if (!value) {
265
+ throw new Error("group target is required");
266
+ }
200
267
  return { kind: "chat_guid", chatGuid: value };
201
268
  }
202
269
 
@@ -221,13 +288,17 @@ export function parseBlueBubblesTarget(raw: string): BlueBubblesTarget {
221
288
 
222
289
  export function parseBlueBubblesAllowTarget(raw: string): BlueBubblesAllowTarget {
223
290
  const trimmed = raw.trim();
224
- if (!trimmed) return { kind: "handle", handle: "" };
291
+ if (!trimmed) {
292
+ return { kind: "handle", handle: "" };
293
+ }
225
294
  const lower = trimmed.toLowerCase();
226
295
 
227
296
  for (const { prefix } of SERVICE_PREFIXES) {
228
297
  if (lower.startsWith(prefix)) {
229
298
  const remainder = stripPrefix(trimmed, prefix);
230
- if (!remainder) return { kind: "handle", handle: "" };
299
+ if (!remainder) {
300
+ return { kind: "handle", handle: "" };
301
+ }
231
302
  return parseBlueBubblesAllowTarget(remainder);
232
303
  }
233
304
  }
@@ -236,29 +307,39 @@ export function parseBlueBubblesAllowTarget(raw: string): BlueBubblesAllowTarget
236
307
  if (lower.startsWith(prefix)) {
237
308
  const value = stripPrefix(trimmed, prefix);
238
309
  const chatId = Number.parseInt(value, 10);
239
- if (Number.isFinite(chatId)) return { kind: "chat_id", chatId };
310
+ if (Number.isFinite(chatId)) {
311
+ return { kind: "chat_id", chatId };
312
+ }
240
313
  }
241
314
  }
242
315
 
243
316
  for (const prefix of CHAT_GUID_PREFIXES) {
244
317
  if (lower.startsWith(prefix)) {
245
318
  const value = stripPrefix(trimmed, prefix);
246
- if (value) return { kind: "chat_guid", chatGuid: value };
319
+ if (value) {
320
+ return { kind: "chat_guid", chatGuid: value };
321
+ }
247
322
  }
248
323
  }
249
324
 
250
325
  for (const prefix of CHAT_IDENTIFIER_PREFIXES) {
251
326
  if (lower.startsWith(prefix)) {
252
327
  const value = stripPrefix(trimmed, prefix);
253
- if (value) return { kind: "chat_identifier", chatIdentifier: value };
328
+ if (value) {
329
+ return { kind: "chat_identifier", chatIdentifier: value };
330
+ }
254
331
  }
255
332
  }
256
333
 
257
334
  if (lower.startsWith("group:")) {
258
335
  const value = stripPrefix(trimmed, "group:");
259
336
  const chatId = Number.parseInt(value, 10);
260
- if (Number.isFinite(chatId)) return { kind: "chat_id", chatId };
261
- if (value) return { kind: "chat_guid", chatGuid: value };
337
+ if (Number.isFinite(chatId)) {
338
+ return { kind: "chat_id", chatId };
339
+ }
340
+ if (value) {
341
+ return { kind: "chat_guid", chatGuid: value };
342
+ }
262
343
  }
263
344
 
264
345
  // Handle chat<digits> pattern (e.g., "chat660250192681427962") as chat_identifier
@@ -283,8 +364,12 @@ export function isAllowedBlueBubblesSender(params: {
283
364
  chatIdentifier?: string | null;
284
365
  }): boolean {
285
366
  const allowFrom = params.allowFrom.map((entry) => String(entry).trim());
286
- if (allowFrom.length === 0) return true;
287
- if (allowFrom.includes("*")) return true;
367
+ if (allowFrom.length === 0) {
368
+ return true;
369
+ }
370
+ if (allowFrom.includes("*")) {
371
+ return true;
372
+ }
288
373
 
289
374
  const senderNormalized = normalizeBlueBubblesHandle(params.sender);
290
375
  const chatId = params.chatId ?? undefined;
@@ -292,16 +377,26 @@ export function isAllowedBlueBubblesSender(params: {
292
377
  const chatIdentifier = params.chatIdentifier?.trim();
293
378
 
294
379
  for (const entry of allowFrom) {
295
- if (!entry) continue;
380
+ if (!entry) {
381
+ continue;
382
+ }
296
383
  const parsed = parseBlueBubblesAllowTarget(entry);
297
384
  if (parsed.kind === "chat_id" && chatId !== undefined) {
298
- if (parsed.chatId === chatId) return true;
385
+ if (parsed.chatId === chatId) {
386
+ return true;
387
+ }
299
388
  } else if (parsed.kind === "chat_guid" && chatGuid) {
300
- if (parsed.chatGuid === chatGuid) return true;
389
+ if (parsed.chatGuid === chatGuid) {
390
+ return true;
391
+ }
301
392
  } else if (parsed.kind === "chat_identifier" && chatIdentifier) {
302
- if (parsed.chatIdentifier === chatIdentifier) return true;
393
+ if (parsed.chatIdentifier === chatIdentifier) {
394
+ return true;
395
+ }
303
396
  } else if (parsed.kind === "handle" && senderNormalized) {
304
- if (parsed.handle === senderNormalized) return true;
397
+ if (parsed.handle === senderNormalized) {
398
+ return true;
399
+ }
305
400
  }
306
401
  }
307
402
  return false;
@@ -316,8 +411,12 @@ export function formatBlueBubblesChatTarget(params: {
316
411
  return `chat_id:${params.chatId}`;
317
412
  }
318
413
  const guid = params.chatGuid?.trim();
319
- if (guid) return `chat_guid:${guid}`;
414
+ if (guid) {
415
+ return `chat_guid:${guid}`;
416
+ }
320
417
  const identifier = params.chatIdentifier?.trim();
321
- if (identifier) return `chat_identifier:${identifier}`;
418
+ if (identifier) {
419
+ return `chat_identifier:${identifier}`;
420
+ }
322
421
  return "";
323
422
  }