spectrum-ts 2.0.0 → 3.1.0
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/dist/{attachment-B4nSrKVd.d.ts → attachment-CEpGtZLm.d.ts} +1 -1
- package/dist/{authoring-BjE5BvlO.d.ts → authoring-CP3vRza8.d.ts} +61 -26
- package/dist/authoring.d.ts +3 -3
- package/dist/authoring.js +5 -5
- package/dist/{chunk-NNY6LMSC.js → chunk-77U6SH5A.js} +1 -1
- package/dist/{chunk-WXY5QP3M.js → chunk-7ON5XHC2.js} +27 -21
- package/dist/{chunk-6BI4PFTP.js → chunk-CHY5YLLV.js} +1 -1
- package/dist/{chunk-Q537JPTG.js → chunk-FA7VA4XN.js} +10 -10
- package/dist/{chunk-NGC4DJIX.js → chunk-L3NUESOW.js} +425 -137
- package/dist/{chunk-2ILTJC35.js → chunk-LQMDV75O.js} +205 -11
- package/dist/{chunk-3B4QH4JG.js → chunk-MHGCPC2V.js} +1 -1
- package/dist/{chunk-U7AWXDH6.js → chunk-NZ5WCMTY.js} +1 -1
- package/dist/{chunk-5LT5J3NR.js → chunk-PSSWQBOH.js} +262 -30
- package/dist/{chunk-U3LXXT3W.js → chunk-Q44CIGG6.js} +20 -8
- package/dist/{chunk-ATNAE7OR.js → chunk-WMG36LHW.js} +676 -159
- package/dist/index.d.ts +107 -56
- package/dist/index.js +29 -182
- package/dist/providers/imessage/index.d.ts +7 -14
- package/dist/providers/imessage/index.js +6 -6
- package/dist/providers/index.d.ts +3 -3
- package/dist/providers/index.js +11 -11
- package/dist/providers/slack/index.d.ts +1 -2
- package/dist/providers/slack/index.js +3 -3
- package/dist/providers/telegram/index.d.ts +3 -5
- package/dist/providers/telegram/index.js +5 -5
- package/dist/providers/terminal/index.d.ts +2 -4
- package/dist/providers/terminal/index.js +5 -5
- package/dist/providers/whatsapp-business/index.d.ts +1 -1
- package/dist/providers/whatsapp-business/index.js +4 -4
- package/dist/{types-Bje8aq1k.d.ts → types-Be0T6E0e.d.ts} +172 -23
- package/dist/{types-BD0-kKyv.d.ts → types-CDYXH2R7.d.ts} +1 -1
- package/package.json +2 -1
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { createRequire as __spectrumCreateRequire } from "node:module"; const require = __spectrumCreateRequire(import.meta.url);
|
|
2
2
|
import {
|
|
3
3
|
asRichlink
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-CHY5YLLV.js";
|
|
5
5
|
import {
|
|
6
6
|
asGroup,
|
|
7
7
|
groupSchema
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-MHGCPC2V.js";
|
|
9
9
|
import {
|
|
10
10
|
asPoll,
|
|
11
11
|
asPollOption
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
} from "./chunk-3GEJYGZK.js";
|
|
16
16
|
import {
|
|
17
17
|
asContact
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-NZ5WCMTY.js";
|
|
19
19
|
import {
|
|
20
20
|
mergeStreams,
|
|
21
21
|
stream
|
|
@@ -28,8 +28,9 @@ import {
|
|
|
28
28
|
UnsupportedError,
|
|
29
29
|
buildPhotoAction,
|
|
30
30
|
definePlatform,
|
|
31
|
+
markdownSchema,
|
|
31
32
|
photoActionSchema
|
|
32
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-L3NUESOW.js";
|
|
33
34
|
import {
|
|
34
35
|
asAttachment,
|
|
35
36
|
asCustom,
|
|
@@ -38,10 +39,13 @@ import {
|
|
|
38
39
|
reactionSchema,
|
|
39
40
|
text,
|
|
40
41
|
textSchema
|
|
41
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-LQMDV75O.js";
|
|
42
43
|
|
|
43
44
|
// src/providers/imessage/index.ts
|
|
44
|
-
import {
|
|
45
|
+
import {
|
|
46
|
+
createClient as createClient2,
|
|
47
|
+
MessageEffect as MessageEffect2
|
|
48
|
+
} from "@photon-ai/advanced-imessage";
|
|
45
49
|
import { IMessageSDK as IMessageSDK2 } from "@photon-ai/imessage-kit";
|
|
46
50
|
import { withSpan } from "@photon-ai/otel";
|
|
47
51
|
|
|
@@ -133,6 +137,7 @@ import {
|
|
|
133
137
|
import z3 from "zod";
|
|
134
138
|
var effectInnerSchema = z3.discriminatedUnion("type", [
|
|
135
139
|
textSchema,
|
|
140
|
+
markdownSchema,
|
|
136
141
|
attachmentSchema
|
|
137
142
|
]);
|
|
138
143
|
var messageEffectSchema = z3.object({
|
|
@@ -153,9 +158,9 @@ function effect(input, messageEffect) {
|
|
|
153
158
|
);
|
|
154
159
|
}
|
|
155
160
|
const inner = await resolveContent(input);
|
|
156
|
-
if (inner.type !== "text" && inner.type !== "attachment") {
|
|
161
|
+
if (inner.type !== "text" && inner.type !== "markdown" && inner.type !== "attachment") {
|
|
157
162
|
throw new Error(
|
|
158
|
-
`imessage effect() only supports text and attachment content, got "${inner.type}"`
|
|
163
|
+
`imessage effect() only supports text, markdown, and attachment content, got "${inner.type}"`
|
|
159
164
|
);
|
|
160
165
|
}
|
|
161
166
|
return messageEffectSchema.parse({
|
|
@@ -349,6 +354,77 @@ async function disposeCloudAuth(clients) {
|
|
|
349
354
|
}
|
|
350
355
|
}
|
|
351
356
|
|
|
357
|
+
// src/providers/imessage/cache.ts
|
|
358
|
+
var DEFAULT_MAX = 1e3;
|
|
359
|
+
var MessageCache = class {
|
|
360
|
+
map = /* @__PURE__ */ new Map();
|
|
361
|
+
max;
|
|
362
|
+
constructor(max = DEFAULT_MAX) {
|
|
363
|
+
this.max = max;
|
|
364
|
+
}
|
|
365
|
+
get(id) {
|
|
366
|
+
return this.map.get(id);
|
|
367
|
+
}
|
|
368
|
+
set(id, message) {
|
|
369
|
+
if (this.map.has(id)) {
|
|
370
|
+
this.map.delete(id);
|
|
371
|
+
}
|
|
372
|
+
this.map.set(id, message);
|
|
373
|
+
if (this.map.size > this.max) {
|
|
374
|
+
const first = this.map.keys().next().value;
|
|
375
|
+
if (first !== void 0) {
|
|
376
|
+
this.map.delete(first);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
clear() {
|
|
381
|
+
this.map.clear();
|
|
382
|
+
}
|
|
383
|
+
};
|
|
384
|
+
var PollCache = class {
|
|
385
|
+
map = /* @__PURE__ */ new Map();
|
|
386
|
+
max;
|
|
387
|
+
constructor(max = DEFAULT_MAX) {
|
|
388
|
+
this.max = max;
|
|
389
|
+
}
|
|
390
|
+
get(id) {
|
|
391
|
+
return this.map.get(id);
|
|
392
|
+
}
|
|
393
|
+
set(id, poll) {
|
|
394
|
+
if (this.map.has(id)) {
|
|
395
|
+
this.map.delete(id);
|
|
396
|
+
}
|
|
397
|
+
this.map.set(id, poll);
|
|
398
|
+
if (this.map.size > this.max) {
|
|
399
|
+
const first = this.map.keys().next().value;
|
|
400
|
+
if (first !== void 0) {
|
|
401
|
+
this.map.delete(first);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
clear() {
|
|
406
|
+
this.map.clear();
|
|
407
|
+
}
|
|
408
|
+
};
|
|
409
|
+
var messageCaches = /* @__PURE__ */ new WeakMap();
|
|
410
|
+
var pollCaches = /* @__PURE__ */ new WeakMap();
|
|
411
|
+
var getMessageCache = (owner) => {
|
|
412
|
+
let cache = messageCaches.get(owner);
|
|
413
|
+
if (!cache) {
|
|
414
|
+
cache = new MessageCache();
|
|
415
|
+
messageCaches.set(owner, cache);
|
|
416
|
+
}
|
|
417
|
+
return cache;
|
|
418
|
+
};
|
|
419
|
+
var getPollCache = (owner) => {
|
|
420
|
+
let cache = pollCaches.get(owner);
|
|
421
|
+
if (!cache) {
|
|
422
|
+
cache = new PollCache();
|
|
423
|
+
pollCaches.set(owner, cache);
|
|
424
|
+
}
|
|
425
|
+
return cache;
|
|
426
|
+
};
|
|
427
|
+
|
|
352
428
|
// src/providers/imessage/local/inbound.ts
|
|
353
429
|
import { setTimeout as sleep } from "timers/promises";
|
|
354
430
|
|
|
@@ -571,6 +647,7 @@ var getMessage2 = async (client, id) => getMessage(client, id);
|
|
|
571
647
|
// src/providers/imessage/remote/ids.ts
|
|
572
648
|
var PART_PREFIX = /^p:(\d+)\//;
|
|
573
649
|
var dmChatGuid = (address) => `any;-;${address}`;
|
|
650
|
+
var chatTypeFromGuid = (guid) => guid.includes(";+;") ? "group" : "dm";
|
|
574
651
|
var toChatGuid = (value) => value;
|
|
575
652
|
var toMessageGuid = (value) => value;
|
|
576
653
|
var formatChildId = (partIndex, parentGuid) => `p:${partIndex}/${parentGuid}`;
|
|
@@ -620,6 +697,7 @@ var sendCustomizedMiniApp = async (remote, spaceId, content) => {
|
|
|
620
697
|
return {
|
|
621
698
|
id: message.guid,
|
|
622
699
|
content,
|
|
700
|
+
direction: "outbound",
|
|
623
701
|
space: { id: spaceId },
|
|
624
702
|
timestamp: message.dateCreated
|
|
625
703
|
};
|
|
@@ -630,77 +708,6 @@ import {
|
|
|
630
708
|
NotFoundError as NotFoundError2
|
|
631
709
|
} from "@photon-ai/advanced-imessage";
|
|
632
710
|
|
|
633
|
-
// src/providers/imessage/cache.ts
|
|
634
|
-
var DEFAULT_MAX = 1e3;
|
|
635
|
-
var MessageCache = class {
|
|
636
|
-
map = /* @__PURE__ */ new Map();
|
|
637
|
-
max;
|
|
638
|
-
constructor(max = DEFAULT_MAX) {
|
|
639
|
-
this.max = max;
|
|
640
|
-
}
|
|
641
|
-
get(id) {
|
|
642
|
-
return this.map.get(id);
|
|
643
|
-
}
|
|
644
|
-
set(id, message) {
|
|
645
|
-
if (this.map.has(id)) {
|
|
646
|
-
this.map.delete(id);
|
|
647
|
-
}
|
|
648
|
-
this.map.set(id, message);
|
|
649
|
-
if (this.map.size > this.max) {
|
|
650
|
-
const first = this.map.keys().next().value;
|
|
651
|
-
if (first !== void 0) {
|
|
652
|
-
this.map.delete(first);
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
clear() {
|
|
657
|
-
this.map.clear();
|
|
658
|
-
}
|
|
659
|
-
};
|
|
660
|
-
var PollCache = class {
|
|
661
|
-
map = /* @__PURE__ */ new Map();
|
|
662
|
-
max;
|
|
663
|
-
constructor(max = DEFAULT_MAX) {
|
|
664
|
-
this.max = max;
|
|
665
|
-
}
|
|
666
|
-
get(id) {
|
|
667
|
-
return this.map.get(id);
|
|
668
|
-
}
|
|
669
|
-
set(id, poll) {
|
|
670
|
-
if (this.map.has(id)) {
|
|
671
|
-
this.map.delete(id);
|
|
672
|
-
}
|
|
673
|
-
this.map.set(id, poll);
|
|
674
|
-
if (this.map.size > this.max) {
|
|
675
|
-
const first = this.map.keys().next().value;
|
|
676
|
-
if (first !== void 0) {
|
|
677
|
-
this.map.delete(first);
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
clear() {
|
|
682
|
-
this.map.clear();
|
|
683
|
-
}
|
|
684
|
-
};
|
|
685
|
-
var messageCaches = /* @__PURE__ */ new WeakMap();
|
|
686
|
-
var pollCaches = /* @__PURE__ */ new WeakMap();
|
|
687
|
-
var getMessageCache = (owner) => {
|
|
688
|
-
let cache = messageCaches.get(owner);
|
|
689
|
-
if (!cache) {
|
|
690
|
-
cache = new MessageCache();
|
|
691
|
-
messageCaches.set(owner, cache);
|
|
692
|
-
}
|
|
693
|
-
return cache;
|
|
694
|
-
};
|
|
695
|
-
var getPollCache = (owner) => {
|
|
696
|
-
let cache = pollCaches.get(owner);
|
|
697
|
-
if (!cache) {
|
|
698
|
-
cache = new PollCache();
|
|
699
|
-
pollCaches.set(owner, cache);
|
|
700
|
-
}
|
|
701
|
-
return cache;
|
|
702
|
-
};
|
|
703
|
-
|
|
704
711
|
// src/providers/imessage/remote/attachments.ts
|
|
705
712
|
import {
|
|
706
713
|
NotFoundError
|
|
@@ -796,16 +803,17 @@ var isIMessageMessage = (value) => {
|
|
|
796
803
|
return false;
|
|
797
804
|
}
|
|
798
805
|
const record = value;
|
|
799
|
-
return typeof record.id === "string" && record.id.length > 0 && typeof record.content === "object" && record.content !== null && typeof record.
|
|
806
|
+
return typeof record.id === "string" && record.id.length > 0 && typeof record.content === "object" && record.content !== null && typeof record.space === "object" && record.space !== null;
|
|
800
807
|
};
|
|
801
808
|
var asProviderGroup = (items) => groupSchema.parse({ type: "group", items });
|
|
802
809
|
var buildMessageBase = (message, chatGuidHint, timestamp, phone) => {
|
|
803
810
|
const chat = resolveChatGuid(message, chatGuidHint);
|
|
804
811
|
return {
|
|
812
|
+
direction: message.isFromMe ? "outbound" : "inbound",
|
|
805
813
|
sender: { id: resolveSenderId(message) },
|
|
806
814
|
space: {
|
|
807
815
|
id: chat,
|
|
808
|
-
type: chat
|
|
816
|
+
type: chatTypeFromGuid(chat),
|
|
809
817
|
phone
|
|
810
818
|
},
|
|
811
819
|
timestamp
|
|
@@ -1090,7 +1098,7 @@ var toReactionMessages = async (client, cache, event, phone) => {
|
|
|
1090
1098
|
sender: { id: senderAddress },
|
|
1091
1099
|
space: {
|
|
1092
1100
|
id: event.chatGuid,
|
|
1093
|
-
type: event.chatGuid
|
|
1101
|
+
type: chatTypeFromGuid(event.chatGuid),
|
|
1094
1102
|
phone
|
|
1095
1103
|
},
|
|
1096
1104
|
timestamp: event.occurredAt,
|
|
@@ -1099,23 +1107,40 @@ var toReactionMessages = async (client, cache, event, phone) => {
|
|
|
1099
1107
|
}
|
|
1100
1108
|
];
|
|
1101
1109
|
};
|
|
1110
|
+
var toSettableReaction = (emoji) => {
|
|
1111
|
+
const native = EMOJI_TO_TAPBACK[emoji];
|
|
1112
|
+
return native ? { kind: native } : { kind: "emoji", emoji };
|
|
1113
|
+
};
|
|
1114
|
+
var tapbackTarget = (target) => ({
|
|
1115
|
+
guid: toMessageGuid(target.parentId ?? target.id),
|
|
1116
|
+
opts: typeof target.partIndex === "number" ? { partIndex: target.partIndex } : void 0
|
|
1117
|
+
});
|
|
1102
1118
|
var reactToMessage = async (remote, spaceId, target, reaction) => {
|
|
1103
|
-
const
|
|
1104
|
-
const
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
+
const { guid, opts } = tapbackTarget(target);
|
|
1120
|
+
const sent = await remote.messages.setReaction(
|
|
1121
|
+
toChatGuid(spaceId),
|
|
1122
|
+
guid,
|
|
1123
|
+
toSettableReaction(reaction),
|
|
1124
|
+
true,
|
|
1125
|
+
opts
|
|
1126
|
+
);
|
|
1127
|
+
return {
|
|
1128
|
+
id: sent.guid,
|
|
1129
|
+
content: asProviderReaction(reaction, target),
|
|
1130
|
+
direction: "outbound",
|
|
1131
|
+
space: { id: spaceId },
|
|
1132
|
+
timestamp: sent.dateCreated
|
|
1133
|
+
};
|
|
1134
|
+
};
|
|
1135
|
+
var unsendReaction = async (remote, spaceId, target, reaction) => {
|
|
1136
|
+
const { guid, opts } = tapbackTarget(target);
|
|
1137
|
+
await remote.messages.setReaction(
|
|
1138
|
+
toChatGuid(spaceId),
|
|
1139
|
+
guid,
|
|
1140
|
+
toSettableReaction(reaction),
|
|
1141
|
+
false,
|
|
1142
|
+
opts
|
|
1143
|
+
);
|
|
1119
1144
|
};
|
|
1120
1145
|
|
|
1121
1146
|
// src/providers/imessage/remote/read.ts
|
|
@@ -1251,17 +1276,319 @@ var ensureM4a = async (buffer, mimeType) => {
|
|
|
1251
1276
|
return transcodeToM4a(buffer);
|
|
1252
1277
|
};
|
|
1253
1278
|
|
|
1279
|
+
// src/providers/imessage/remote/markdown.ts
|
|
1280
|
+
import { Marked } from "marked";
|
|
1281
|
+
var markdownLexer = new Marked();
|
|
1282
|
+
var BULLET = "\u2022 ";
|
|
1283
|
+
var HR_LINE = "\u2014\u2014\u2014";
|
|
1284
|
+
var NESTED_LIST_INDENT = " ";
|
|
1285
|
+
var BLOCK_SEPARATOR = "\n\n";
|
|
1286
|
+
var TABLE_CELL_SEPARATOR = " | ";
|
|
1287
|
+
var DEFAULT_LIST_START = 1;
|
|
1288
|
+
var LEADING_WHITESPACE = /^\s+/;
|
|
1289
|
+
var TRAILING_WHITESPACE = /\s+$/;
|
|
1290
|
+
var MONOSPACE_UPPER_A = 120432;
|
|
1291
|
+
var MONOSPACE_LOWER_A = 120458;
|
|
1292
|
+
var MONOSPACE_DIGIT_ZERO = 120822;
|
|
1293
|
+
var UPPER_A = 65;
|
|
1294
|
+
var UPPER_Z = 90;
|
|
1295
|
+
var LOWER_A = 97;
|
|
1296
|
+
var LOWER_Z = 122;
|
|
1297
|
+
var DIGIT_ZERO = 48;
|
|
1298
|
+
var DIGIT_NINE = 57;
|
|
1299
|
+
var monospaceCodePoint = (codePoint) => {
|
|
1300
|
+
if (codePoint >= UPPER_A && codePoint <= UPPER_Z) {
|
|
1301
|
+
return MONOSPACE_UPPER_A + (codePoint - UPPER_A);
|
|
1302
|
+
}
|
|
1303
|
+
if (codePoint >= LOWER_A && codePoint <= LOWER_Z) {
|
|
1304
|
+
return MONOSPACE_LOWER_A + (codePoint - LOWER_A);
|
|
1305
|
+
}
|
|
1306
|
+
if (codePoint >= DIGIT_ZERO && codePoint <= DIGIT_NINE) {
|
|
1307
|
+
return MONOSPACE_DIGIT_ZERO + (codePoint - DIGIT_ZERO);
|
|
1308
|
+
}
|
|
1309
|
+
return codePoint;
|
|
1310
|
+
};
|
|
1311
|
+
var toMonospace = (text2) => {
|
|
1312
|
+
let out = "";
|
|
1313
|
+
for (const char of text2) {
|
|
1314
|
+
const codePoint = char.codePointAt(0);
|
|
1315
|
+
out += codePoint === void 0 ? char : String.fromCodePoint(monospaceCodePoint(codePoint));
|
|
1316
|
+
}
|
|
1317
|
+
return out;
|
|
1318
|
+
};
|
|
1319
|
+
var STYLE_ORDER = ["bold", "italic", "strikethrough"];
|
|
1320
|
+
var plain = (text2) => ({ text: text2, styles: [] });
|
|
1321
|
+
var withStyle = (spans, style) => spans.map(
|
|
1322
|
+
(span) => span.styles.includes(style) ? span : { ...span, styles: [...span.styles, style] }
|
|
1323
|
+
);
|
|
1324
|
+
var asLink = (spans) => spans.map((span) => ({ ...span, link: true }));
|
|
1325
|
+
var spanText = (spans) => {
|
|
1326
|
+
let out = "";
|
|
1327
|
+
for (const span of spans) {
|
|
1328
|
+
out += span.text;
|
|
1329
|
+
}
|
|
1330
|
+
return out;
|
|
1331
|
+
};
|
|
1332
|
+
var joinSpans = (blocks, separator) => {
|
|
1333
|
+
const out = [];
|
|
1334
|
+
for (const [index, block] of blocks.entries()) {
|
|
1335
|
+
if (index > 0) {
|
|
1336
|
+
out.push(plain(separator));
|
|
1337
|
+
}
|
|
1338
|
+
out.push(...block);
|
|
1339
|
+
}
|
|
1340
|
+
return out;
|
|
1341
|
+
};
|
|
1342
|
+
var splitSpanLines = (spans) => {
|
|
1343
|
+
let current = [];
|
|
1344
|
+
const lines = [current];
|
|
1345
|
+
for (const span of spans) {
|
|
1346
|
+
const parts = span.text.split("\n");
|
|
1347
|
+
for (const [index, part] of parts.entries()) {
|
|
1348
|
+
if (index > 0) {
|
|
1349
|
+
current = [];
|
|
1350
|
+
lines.push(current);
|
|
1351
|
+
}
|
|
1352
|
+
if (part) {
|
|
1353
|
+
current.push({ ...span, text: part });
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
return lines;
|
|
1358
|
+
};
|
|
1359
|
+
var asMarkedToken = (token) => token;
|
|
1360
|
+
var checkboxPrefix = (item) => {
|
|
1361
|
+
if (!item.task) {
|
|
1362
|
+
return "";
|
|
1363
|
+
}
|
|
1364
|
+
return item.checked ? "[x] " : "[ ] ";
|
|
1365
|
+
};
|
|
1366
|
+
var listMarker = (list, index) => {
|
|
1367
|
+
if (!list.ordered) {
|
|
1368
|
+
return BULLET;
|
|
1369
|
+
}
|
|
1370
|
+
const start = list.start === "" ? DEFAULT_LIST_START : list.start;
|
|
1371
|
+
return `${start + index}. `;
|
|
1372
|
+
};
|
|
1373
|
+
var renderLink = (token) => {
|
|
1374
|
+
if (token.text === token.href) {
|
|
1375
|
+
return [{ text: token.href, styles: [], link: true }];
|
|
1376
|
+
}
|
|
1377
|
+
return [
|
|
1378
|
+
...asLink(renderInlineTokens(token.tokens)),
|
|
1379
|
+
{ text: ` (${token.href})`, styles: [], link: true }
|
|
1380
|
+
];
|
|
1381
|
+
};
|
|
1382
|
+
var renderImage = (token) => [
|
|
1383
|
+
{
|
|
1384
|
+
text: token.text ? `${token.text} (${token.href})` : token.href,
|
|
1385
|
+
styles: [],
|
|
1386
|
+
link: true
|
|
1387
|
+
}
|
|
1388
|
+
];
|
|
1389
|
+
var renderInlineToken = (token) => {
|
|
1390
|
+
switch (token.type) {
|
|
1391
|
+
case "strong":
|
|
1392
|
+
return withStyle(renderInlineTokens(token.tokens), "bold");
|
|
1393
|
+
case "em":
|
|
1394
|
+
return withStyle(renderInlineTokens(token.tokens), "italic");
|
|
1395
|
+
case "del":
|
|
1396
|
+
return withStyle(renderInlineTokens(token.tokens), "strikethrough");
|
|
1397
|
+
case "codespan":
|
|
1398
|
+
return [plain(toMonospace(token.text))];
|
|
1399
|
+
case "br":
|
|
1400
|
+
return [plain("\n")];
|
|
1401
|
+
case "link":
|
|
1402
|
+
return renderLink(token);
|
|
1403
|
+
case "image":
|
|
1404
|
+
return renderImage(token);
|
|
1405
|
+
case "escape":
|
|
1406
|
+
return [plain(token.text)];
|
|
1407
|
+
case "text":
|
|
1408
|
+
return token.tokens ? renderInlineTokens(token.tokens) : [plain(token.text)];
|
|
1409
|
+
// Raw HTML in markdown source stays literal — styled text has no markup.
|
|
1410
|
+
case "html":
|
|
1411
|
+
return [plain(token.text)];
|
|
1412
|
+
// Task-item checkboxes are rendered from `ListItem.task`/`checked`.
|
|
1413
|
+
case "checkbox":
|
|
1414
|
+
return [];
|
|
1415
|
+
default:
|
|
1416
|
+
return "raw" in token ? [plain(String(token.raw))] : [];
|
|
1417
|
+
}
|
|
1418
|
+
};
|
|
1419
|
+
var renderInlineTokens = (tokens) => {
|
|
1420
|
+
const out = [];
|
|
1421
|
+
for (const token of tokens) {
|
|
1422
|
+
out.push(...renderInlineToken(asMarkedToken(token)));
|
|
1423
|
+
}
|
|
1424
|
+
return out;
|
|
1425
|
+
};
|
|
1426
|
+
var renderBlockquote = (quote) => {
|
|
1427
|
+
const lines = splitSpanLines(renderBlockTokens(quote.tokens));
|
|
1428
|
+
const out = [];
|
|
1429
|
+
for (const [index, line] of lines.entries()) {
|
|
1430
|
+
if (index > 0) {
|
|
1431
|
+
out.push(plain("\n"));
|
|
1432
|
+
}
|
|
1433
|
+
out.push(plain(line.length > 0 ? "> " : ">"), ...line);
|
|
1434
|
+
}
|
|
1435
|
+
return out;
|
|
1436
|
+
};
|
|
1437
|
+
var renderList = (list) => {
|
|
1438
|
+
const out = [];
|
|
1439
|
+
for (const [index, item] of list.items.entries()) {
|
|
1440
|
+
const prefix = `${listMarker(list, index)}${checkboxPrefix(item)}`;
|
|
1441
|
+
const blocks = [];
|
|
1442
|
+
for (const token of item.tokens) {
|
|
1443
|
+
const rendered = renderBlockToken(asMarkedToken(token));
|
|
1444
|
+
if (spanText(rendered)) {
|
|
1445
|
+
blocks.push(rendered);
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
const [first = [], ...rest] = splitSpanLines(joinSpans(blocks, "\n"));
|
|
1449
|
+
if (out.length > 0) {
|
|
1450
|
+
out.push(plain("\n"));
|
|
1451
|
+
}
|
|
1452
|
+
out.push(plain(prefix), ...first);
|
|
1453
|
+
for (const line of rest) {
|
|
1454
|
+
out.push(plain(`
|
|
1455
|
+
${NESTED_LIST_INDENT}`), ...line);
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
return out;
|
|
1459
|
+
};
|
|
1460
|
+
var renderTable = (table) => {
|
|
1461
|
+
const out = [];
|
|
1462
|
+
const pushRow = (cells, rowIndex) => {
|
|
1463
|
+
if (rowIndex > 0) {
|
|
1464
|
+
out.push(plain("\n"));
|
|
1465
|
+
}
|
|
1466
|
+
for (const [cellIndex, cell] of cells.entries()) {
|
|
1467
|
+
if (cellIndex > 0) {
|
|
1468
|
+
out.push(plain(TABLE_CELL_SEPARATOR));
|
|
1469
|
+
}
|
|
1470
|
+
out.push(...renderInlineTokens(cell.tokens));
|
|
1471
|
+
}
|
|
1472
|
+
};
|
|
1473
|
+
pushRow(table.header, 0);
|
|
1474
|
+
for (const [index, row] of table.rows.entries()) {
|
|
1475
|
+
pushRow(row, index + 1);
|
|
1476
|
+
}
|
|
1477
|
+
return out;
|
|
1478
|
+
};
|
|
1479
|
+
var renderBlockToken = (token) => {
|
|
1480
|
+
switch (token.type) {
|
|
1481
|
+
// iMessage formatting has no heading sizes; bold is the conventional
|
|
1482
|
+
// stand-in (Telegram precedent).
|
|
1483
|
+
case "heading":
|
|
1484
|
+
return withStyle(renderInlineTokens(token.tokens), "bold");
|
|
1485
|
+
case "paragraph":
|
|
1486
|
+
return renderInlineTokens(token.tokens);
|
|
1487
|
+
case "code":
|
|
1488
|
+
return [plain(toMonospace(token.text))];
|
|
1489
|
+
case "blockquote":
|
|
1490
|
+
return renderBlockquote(token);
|
|
1491
|
+
case "list":
|
|
1492
|
+
return renderList(token);
|
|
1493
|
+
case "table":
|
|
1494
|
+
return renderTable(token);
|
|
1495
|
+
case "hr":
|
|
1496
|
+
return [plain(HR_LINE)];
|
|
1497
|
+
case "space":
|
|
1498
|
+
case "def":
|
|
1499
|
+
return [];
|
|
1500
|
+
default:
|
|
1501
|
+
return renderInlineToken(token);
|
|
1502
|
+
}
|
|
1503
|
+
};
|
|
1504
|
+
var renderBlockTokens = (tokens) => {
|
|
1505
|
+
const blocks = [];
|
|
1506
|
+
for (const token of tokens) {
|
|
1507
|
+
const rendered = renderBlockToken(asMarkedToken(token));
|
|
1508
|
+
if (spanText(rendered)) {
|
|
1509
|
+
blocks.push(rendered);
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
return joinSpans(blocks, BLOCK_SEPARATOR);
|
|
1513
|
+
};
|
|
1514
|
+
var trimSpans = (spans) => {
|
|
1515
|
+
const trimmed = [...spans];
|
|
1516
|
+
while (trimmed.length > 0) {
|
|
1517
|
+
const first = trimmed.at(0);
|
|
1518
|
+
const text2 = first?.text.replace(LEADING_WHITESPACE, "");
|
|
1519
|
+
if (first && text2) {
|
|
1520
|
+
trimmed[0] = { ...first, text: text2 };
|
|
1521
|
+
break;
|
|
1522
|
+
}
|
|
1523
|
+
trimmed.shift();
|
|
1524
|
+
}
|
|
1525
|
+
while (trimmed.length > 0) {
|
|
1526
|
+
const last = trimmed.at(-1);
|
|
1527
|
+
const text2 = last?.text.replace(TRAILING_WHITESPACE, "");
|
|
1528
|
+
if (last && text2) {
|
|
1529
|
+
trimmed[trimmed.length - 1] = { ...last, text: text2 };
|
|
1530
|
+
break;
|
|
1531
|
+
}
|
|
1532
|
+
trimmed.pop();
|
|
1533
|
+
}
|
|
1534
|
+
return trimmed;
|
|
1535
|
+
};
|
|
1536
|
+
var finalize = (spans) => {
|
|
1537
|
+
let text2 = "";
|
|
1538
|
+
let hasLinks = false;
|
|
1539
|
+
const open = /* @__PURE__ */ new Map();
|
|
1540
|
+
const ranges = [];
|
|
1541
|
+
const close = (style, end) => {
|
|
1542
|
+
const start = open.get(style);
|
|
1543
|
+
open.delete(style);
|
|
1544
|
+
if (start !== void 0 && end > start) {
|
|
1545
|
+
ranges.push({ type: style, start, length: end - start });
|
|
1546
|
+
}
|
|
1547
|
+
};
|
|
1548
|
+
for (const span of spans) {
|
|
1549
|
+
if (!span.text) {
|
|
1550
|
+
continue;
|
|
1551
|
+
}
|
|
1552
|
+
hasLinks ||= span.link === true;
|
|
1553
|
+
const offset = text2.length;
|
|
1554
|
+
for (const style of STYLE_ORDER) {
|
|
1555
|
+
if (span.styles.includes(style)) {
|
|
1556
|
+
if (!open.has(style)) {
|
|
1557
|
+
open.set(style, offset);
|
|
1558
|
+
}
|
|
1559
|
+
} else {
|
|
1560
|
+
close(style, offset);
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
text2 += span.text;
|
|
1564
|
+
}
|
|
1565
|
+
for (const style of STYLE_ORDER) {
|
|
1566
|
+
close(style, text2.length);
|
|
1567
|
+
}
|
|
1568
|
+
ranges.sort(
|
|
1569
|
+
(a, b) => a.start - b.start || STYLE_ORDER.indexOf(a.type) - STYLE_ORDER.indexOf(b.type)
|
|
1570
|
+
);
|
|
1571
|
+
return { text: text2, formatting: ranges, hasLinks };
|
|
1572
|
+
};
|
|
1573
|
+
var markdownToIMessageText = (markdown) => finalize(trimSpans(renderBlockTokens(markdownLexer.lexer(markdown))));
|
|
1574
|
+
|
|
1254
1575
|
// src/providers/imessage/remote/send.ts
|
|
1255
1576
|
var GROUP_ITEM_ALLOWED = /* @__PURE__ */ new Set([
|
|
1256
1577
|
"text",
|
|
1578
|
+
"markdown",
|
|
1257
1579
|
"attachment",
|
|
1258
1580
|
"contact",
|
|
1259
1581
|
"voice"
|
|
1260
1582
|
]);
|
|
1583
|
+
var GROUP_TEXT_TYPES = /* @__PURE__ */ new Set([
|
|
1584
|
+
"text",
|
|
1585
|
+
"markdown"
|
|
1586
|
+
]);
|
|
1261
1587
|
var MAX_GROUP_TEXT_ITEMS = 1;
|
|
1262
1588
|
var outboundRecord = (spaceId, id, content, timestamp, extras) => ({
|
|
1263
1589
|
id,
|
|
1264
1590
|
content,
|
|
1591
|
+
direction: "outbound",
|
|
1265
1592
|
space: { id: spaceId },
|
|
1266
1593
|
timestamp,
|
|
1267
1594
|
...extras
|
|
@@ -1274,6 +1601,18 @@ var providerGroup = (items) => asGroup({ items });
|
|
|
1274
1601
|
var withReply = (options, replyTo) => replyTo ? { ...options, replyTo } : options;
|
|
1275
1602
|
var replyOptions = (replyTo) => replyTo ? { replyTo } : void 0;
|
|
1276
1603
|
var effectOption = (effect2) => effect2 ? { effect: effect2 } : {};
|
|
1604
|
+
var formattingOption = (formatting) => formatting.length > 0 ? { formatting } : {};
|
|
1605
|
+
var dataDetectionOption = (hasLinks) => hasLinks ? { enableDataDetection: true } : {};
|
|
1606
|
+
var renderMarkdown = (markdown) => {
|
|
1607
|
+
const rendered = markdownToIMessageText(markdown);
|
|
1608
|
+
if (!rendered.text) {
|
|
1609
|
+
throw unsupportedRemoteContent(
|
|
1610
|
+
"markdown",
|
|
1611
|
+
"renders to empty text \u2014 nothing to send"
|
|
1612
|
+
);
|
|
1613
|
+
}
|
|
1614
|
+
return rendered;
|
|
1615
|
+
};
|
|
1277
1616
|
var replyTargetFromId = (messageId) => {
|
|
1278
1617
|
const childRef = parseChildId(messageId);
|
|
1279
1618
|
if (childRef) {
|
|
@@ -1331,6 +1670,22 @@ var sendContent = async (remote, spaceId, chat, content, replyTo, effect2) => {
|
|
|
1331
1670
|
);
|
|
1332
1671
|
return outboundMessage(spaceId, message, content);
|
|
1333
1672
|
}
|
|
1673
|
+
case "markdown": {
|
|
1674
|
+
const rendered = renderMarkdown(content.markdown);
|
|
1675
|
+
const message = await remote.messages.sendText(
|
|
1676
|
+
chat,
|
|
1677
|
+
rendered.text,
|
|
1678
|
+
withReply(
|
|
1679
|
+
{
|
|
1680
|
+
...effectOption(effect2),
|
|
1681
|
+
...formattingOption(rendered.formatting),
|
|
1682
|
+
...dataDetectionOption(rendered.hasLinks)
|
|
1683
|
+
},
|
|
1684
|
+
replyTo
|
|
1685
|
+
)
|
|
1686
|
+
);
|
|
1687
|
+
return outboundMessage(spaceId, message, content);
|
|
1688
|
+
}
|
|
1334
1689
|
case "richlink": {
|
|
1335
1690
|
const message = await remote.messages.sendText(
|
|
1336
1691
|
chat,
|
|
@@ -1395,7 +1750,7 @@ var validateGroupContent = (content) => {
|
|
|
1395
1750
|
`"${itemType}" items are not supported inside a group`
|
|
1396
1751
|
);
|
|
1397
1752
|
}
|
|
1398
|
-
if (itemType
|
|
1753
|
+
if (GROUP_TEXT_TYPES.has(itemType) && ++textCount > MAX_GROUP_TEXT_ITEMS) {
|
|
1399
1754
|
throw unsupportedRemoteContent(
|
|
1400
1755
|
"group",
|
|
1401
1756
|
`groups can contain at most ${MAX_GROUP_TEXT_ITEMS} text item`
|
|
@@ -1407,6 +1762,10 @@ var resolvePart = async (remote, content) => {
|
|
|
1407
1762
|
switch (content.type) {
|
|
1408
1763
|
case "text":
|
|
1409
1764
|
return { text: content.text };
|
|
1765
|
+
case "markdown": {
|
|
1766
|
+
const rendered = renderMarkdown(content.markdown);
|
|
1767
|
+
return { text: rendered.text, ...formattingOption(rendered.formatting) };
|
|
1768
|
+
}
|
|
1410
1769
|
case "attachment": {
|
|
1411
1770
|
const { guid, name } = await uploadAttachment(remote, content);
|
|
1412
1771
|
return { attachmentGuid: guid, attachmentName: name };
|
|
@@ -1469,20 +1828,29 @@ var editMessage = async (remote, spaceId, msgId, content) => {
|
|
|
1469
1828
|
childRef ? { partIndex: childRef.partIndex } : void 0
|
|
1470
1829
|
);
|
|
1471
1830
|
};
|
|
1831
|
+
var unsendMessage = async (remote, spaceId, msgId) => {
|
|
1832
|
+
const childRef = parseChildId(msgId);
|
|
1833
|
+
await remote.messages.unsend(
|
|
1834
|
+
toChatGuid(spaceId),
|
|
1835
|
+
toMessageGuid(childRef?.parentGuid ?? msgId),
|
|
1836
|
+
childRef ? { partIndex: childRef.partIndex } : void 0
|
|
1837
|
+
);
|
|
1838
|
+
};
|
|
1472
1839
|
|
|
1473
1840
|
// src/providers/imessage/remote/stream.ts
|
|
1474
1841
|
import {
|
|
1475
|
-
AuthenticationError,
|
|
1476
|
-
IMessageError,
|
|
1477
|
-
NotFoundError as NotFoundError3,
|
|
1478
1842
|
ValidationError
|
|
1479
1843
|
} from "@photon-ai/advanced-imessage";
|
|
1844
|
+
import { sanitizePhone } from "@photon-ai/otel";
|
|
1480
1845
|
|
|
1481
1846
|
// src/utils/resumable-stream.ts
|
|
1847
|
+
import { createLogger } from "@photon-ai/otel";
|
|
1482
1848
|
var CATCH_UP_PAGE_SIZE = 100;
|
|
1483
1849
|
var MAX_BUFFERED_LIVE_EVENTS = 1e3;
|
|
1484
1850
|
var RECONNECT_INITIAL_DELAY_MS = 500;
|
|
1485
1851
|
var RECONNECT_MAX_DELAY_MS = 3e4;
|
|
1852
|
+
var PERSISTENT_FAILURE_ERROR_THRESHOLD = 5;
|
|
1853
|
+
var log = createLogger("spectrum.stream");
|
|
1486
1854
|
var RetryableStreamError = class extends Error {
|
|
1487
1855
|
constructor(message) {
|
|
1488
1856
|
super(message);
|
|
@@ -1495,6 +1863,12 @@ var LiveBufferOverflowError = class extends RetryableStreamError {
|
|
|
1495
1863
|
this.name = "LiveBufferOverflowError";
|
|
1496
1864
|
}
|
|
1497
1865
|
};
|
|
1866
|
+
var CursorRejectedError = class extends Error {
|
|
1867
|
+
constructor(cause) {
|
|
1868
|
+
super("Server rejected resume cursor", { cause });
|
|
1869
|
+
this.name = "CursorRejectedError";
|
|
1870
|
+
}
|
|
1871
|
+
};
|
|
1498
1872
|
var closeIterable = async (iterable) => {
|
|
1499
1873
|
if (!iterable) {
|
|
1500
1874
|
return;
|
|
@@ -1502,7 +1876,15 @@ var closeIterable = async (iterable) => {
|
|
|
1502
1876
|
await iterable.close?.();
|
|
1503
1877
|
};
|
|
1504
1878
|
var ignoreCleanupError = () => void 0;
|
|
1505
|
-
var jitterDelay = (delayMs) => Math.random() *
|
|
1879
|
+
var jitterDelay = (delayMs) => delayMs * (0.5 + Math.random() * 0.5);
|
|
1880
|
+
var errorMessage = (error) => error instanceof Error ? error.message : String(error);
|
|
1881
|
+
async function* throwOnCursorRejection(source, isCursorRejected) {
|
|
1882
|
+
try {
|
|
1883
|
+
yield* source;
|
|
1884
|
+
} catch (error) {
|
|
1885
|
+
throw isCursorRejected(error) ? new CursorRejectedError(error) : error;
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1506
1888
|
var numericCursor = (cursor) => {
|
|
1507
1889
|
if (!cursor) {
|
|
1508
1890
|
return;
|
|
@@ -1520,15 +1902,23 @@ var resumableOrderedStream = (options) => stream((emit, end) => {
|
|
|
1520
1902
|
const bufferLimit = options.bufferLimit ?? MAX_BUFFERED_LIVE_EVENTS;
|
|
1521
1903
|
const initialRetryDelayMs = options.initialRetryDelayMs ?? RECONNECT_INITIAL_DELAY_MS;
|
|
1522
1904
|
const maxRetryDelayMs = options.maxRetryDelayMs ?? RECONNECT_MAX_DELAY_MS;
|
|
1905
|
+
const jitter = options.jitter ?? jitterDelay;
|
|
1906
|
+
const label = options.label;
|
|
1523
1907
|
let activeLive;
|
|
1524
1908
|
let closed = false;
|
|
1909
|
+
let failedAttempts = 0;
|
|
1525
1910
|
let lastCursor;
|
|
1526
1911
|
let retryDelayMs = initialRetryDelayMs;
|
|
1527
1912
|
let sleepTimer;
|
|
1528
1913
|
let wakeSleep;
|
|
1529
1914
|
const deliveredSinceCursor = /* @__PURE__ */ new Set();
|
|
1530
|
-
const
|
|
1915
|
+
const noteRecovery = () => {
|
|
1531
1916
|
retryDelayMs = initialRetryDelayMs;
|
|
1917
|
+
if (failedAttempts === 0) {
|
|
1918
|
+
return;
|
|
1919
|
+
}
|
|
1920
|
+
log.info("stream recovered", { attempts: failedAttempts, label });
|
|
1921
|
+
failedAttempts = 0;
|
|
1532
1922
|
};
|
|
1533
1923
|
const advanceCursor = (cursor, clearDelivered) => {
|
|
1534
1924
|
if (!cursor || cursor === lastCursor || isCursorRegression(cursor, lastCursor)) {
|
|
@@ -1549,17 +1939,17 @@ var resumableOrderedStream = (options) => stream((emit, end) => {
|
|
|
1549
1939
|
advanceCursor(item.cursor, clearOnCursorAdvance);
|
|
1550
1940
|
deliveredSinceCursor.add(item.id);
|
|
1551
1941
|
if (resetRetry) {
|
|
1552
|
-
|
|
1942
|
+
noteRecovery();
|
|
1553
1943
|
}
|
|
1554
1944
|
};
|
|
1555
|
-
const
|
|
1945
|
+
const isCursorRejected = (error) => options.isCursorRejectedError?.(error) === true;
|
|
1556
1946
|
const sleep2 = async (delayMs) => {
|
|
1557
1947
|
if (delayMs <= 0 || closed) {
|
|
1558
1948
|
return;
|
|
1559
1949
|
}
|
|
1560
1950
|
await new Promise((resolve) => {
|
|
1561
1951
|
wakeSleep = resolve;
|
|
1562
|
-
sleepTimer = setTimeout(resolve,
|
|
1952
|
+
sleepTimer = setTimeout(resolve, jitter(delayMs));
|
|
1563
1953
|
});
|
|
1564
1954
|
sleepTimer = void 0;
|
|
1565
1955
|
wakeSleep = void 0;
|
|
@@ -1577,6 +1967,37 @@ var resumableOrderedStream = (options) => stream((emit, end) => {
|
|
|
1577
1967
|
retryDelayMs = Math.min(retryDelayMs * 2, maxRetryDelayMs);
|
|
1578
1968
|
return delay;
|
|
1579
1969
|
};
|
|
1970
|
+
const handleFailure = (error) => {
|
|
1971
|
+
failedAttempts += 1;
|
|
1972
|
+
const delayMs = nextRetryDelay();
|
|
1973
|
+
if (error instanceof CursorRejectedError) {
|
|
1974
|
+
lastCursor = void 0;
|
|
1975
|
+
deliveredSinceCursor.clear();
|
|
1976
|
+
log.warn(
|
|
1977
|
+
"resume cursor rejected; accepting event gap and resuming live",
|
|
1978
|
+
{
|
|
1979
|
+
attempt: failedAttempts,
|
|
1980
|
+
delayMs,
|
|
1981
|
+
error: errorMessage(error.cause),
|
|
1982
|
+
label
|
|
1983
|
+
}
|
|
1984
|
+
);
|
|
1985
|
+
return delayMs;
|
|
1986
|
+
}
|
|
1987
|
+
const attrs = {
|
|
1988
|
+
attempt: failedAttempts,
|
|
1989
|
+
delayMs,
|
|
1990
|
+
error: errorMessage(error),
|
|
1991
|
+
hasCursor: lastCursor !== void 0,
|
|
1992
|
+
label
|
|
1993
|
+
};
|
|
1994
|
+
if (failedAttempts >= PERSISTENT_FAILURE_ERROR_THRESHOLD) {
|
|
1995
|
+
log.error("stream persistently failing; still retrying", attrs, error);
|
|
1996
|
+
return delayMs;
|
|
1997
|
+
}
|
|
1998
|
+
log.warn("stream interrupted; reconnecting", attrs);
|
|
1999
|
+
return delayMs;
|
|
2000
|
+
};
|
|
1580
2001
|
const consumeLive = async () => {
|
|
1581
2002
|
const live = options.subscribeLive(lastCursor);
|
|
1582
2003
|
activeLive = live;
|
|
@@ -1625,15 +2046,17 @@ var resumableOrderedStream = (options) => stream((emit, end) => {
|
|
|
1625
2046
|
};
|
|
1626
2047
|
};
|
|
1627
2048
|
const replayMissed = async (cursor, getLiveError) => {
|
|
1628
|
-
|
|
1629
|
-
limit: catchUpPageSize
|
|
1630
|
-
|
|
2049
|
+
const missed = throwOnCursorRejection(
|
|
2050
|
+
options.fetchMissed(cursor, { limit: catchUpPageSize }),
|
|
2051
|
+
isCursorRejected
|
|
2052
|
+
);
|
|
2053
|
+
for await (const event of missed) {
|
|
1631
2054
|
throwLiveError(getLiveError());
|
|
1632
2055
|
await deliverItem(await options.processMissed(event), false, false);
|
|
1633
2056
|
}
|
|
1634
2057
|
throwLiveError(getLiveError());
|
|
1635
2058
|
};
|
|
1636
|
-
const flushLiveBuffer = async (liveBuffer, getLiveError) => {
|
|
2059
|
+
const flushLiveBuffer = async (liveBuffer, getLiveError, stopBuffering) => {
|
|
1637
2060
|
let index = 0;
|
|
1638
2061
|
let lastFlushedId;
|
|
1639
2062
|
while (index < liveBuffer.length) {
|
|
@@ -1649,7 +2072,8 @@ var resumableOrderedStream = (options) => stream((emit, end) => {
|
|
|
1649
2072
|
}
|
|
1650
2073
|
liveBuffer.length = 0;
|
|
1651
2074
|
throwLiveError(getLiveError());
|
|
1652
|
-
|
|
2075
|
+
compactDeliveredIds(lastFlushedId);
|
|
2076
|
+
stopBuffering();
|
|
1653
2077
|
};
|
|
1654
2078
|
const compactDeliveredIds = (lastId) => {
|
|
1655
2079
|
if (!lastId) {
|
|
@@ -1666,13 +2090,10 @@ var resumableOrderedStream = (options) => stream((emit, end) => {
|
|
|
1666
2090
|
const livePump = startLivePump(live, () => buffering, liveBuffer);
|
|
1667
2091
|
try {
|
|
1668
2092
|
await replayMissed(cursor, livePump.getError);
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
);
|
|
1673
|
-
compactDeliveredIds(lastFlushedId);
|
|
1674
|
-
buffering = false;
|
|
1675
|
-
resetRetryDelay();
|
|
2093
|
+
await flushLiveBuffer(liveBuffer, livePump.getError, () => {
|
|
2094
|
+
buffering = false;
|
|
2095
|
+
});
|
|
2096
|
+
noteRecovery();
|
|
1676
2097
|
await livePump.pump;
|
|
1677
2098
|
throwLiveError(livePump.getError());
|
|
1678
2099
|
} finally {
|
|
@@ -1693,21 +2114,18 @@ var resumableOrderedStream = (options) => stream((emit, end) => {
|
|
|
1693
2114
|
await consumeLive();
|
|
1694
2115
|
}
|
|
1695
2116
|
} catch (error) {
|
|
1696
|
-
await closeIterable(activeLive);
|
|
2117
|
+
await closeIterable(activeLive).catch(ignoreCleanupError);
|
|
1697
2118
|
activeLive = void 0;
|
|
1698
2119
|
if (closed) {
|
|
1699
2120
|
break;
|
|
1700
2121
|
}
|
|
1701
|
-
|
|
1702
|
-
end(error);
|
|
1703
|
-
return;
|
|
1704
|
-
}
|
|
1705
|
-
await sleep2(nextRetryDelay());
|
|
2122
|
+
await sleep2(handleFailure(error));
|
|
1706
2123
|
}
|
|
1707
2124
|
}
|
|
1708
2125
|
end();
|
|
1709
2126
|
};
|
|
1710
2127
|
const pump = run().catch((error) => {
|
|
2128
|
+
log.error("resumable stream loop crashed", { label }, error);
|
|
1711
2129
|
if (!closed) {
|
|
1712
2130
|
end(error);
|
|
1713
2131
|
}
|
|
@@ -1840,7 +2258,7 @@ var buildPollOptionMessage = (input) => {
|
|
|
1840
2258
|
sender: { id: input.senderAddress },
|
|
1841
2259
|
space: {
|
|
1842
2260
|
id: input.chatGuid,
|
|
1843
|
-
type: input.chatGuid
|
|
2261
|
+
type: chatTypeFromGuid(input.chatGuid),
|
|
1844
2262
|
phone: input.phone
|
|
1845
2263
|
},
|
|
1846
2264
|
timestamp: input.event.occurredAt,
|
|
@@ -1896,15 +2314,8 @@ var toPollDeltaMessages = async (client, pollCache, event, phone) => {
|
|
|
1896
2314
|
};
|
|
1897
2315
|
|
|
1898
2316
|
// src/providers/imessage/remote/stream.ts
|
|
1899
|
-
var
|
|
1900
|
-
|
|
1901
|
-
return false;
|
|
1902
|
-
}
|
|
1903
|
-
if (error instanceof IMessageError) {
|
|
1904
|
-
return true;
|
|
1905
|
-
}
|
|
1906
|
-
return false;
|
|
1907
|
-
};
|
|
2317
|
+
var isCursorRejectedIMessageError = (error) => error instanceof ValidationError;
|
|
2318
|
+
var streamLabel = (kind, phone) => `imessage.${kind}:${phone === SHARED_PHONE ? phone : sanitizePhone(phone)}`;
|
|
1908
2319
|
var isEventFromCurrentAccount = (event, phone) => event.isFromMe || phone !== SHARED_PHONE && event.actor?.address !== void 0 && event.actor.address === phone;
|
|
1909
2320
|
var toMessageItem = async (client, event, phone, cursor, onInbound) => {
|
|
1910
2321
|
if (event.type === "message.received") {
|
|
@@ -2007,7 +2418,8 @@ var withClose = (source, cursor) => Object.assign(afterCursor(source, cursor), {
|
|
|
2007
2418
|
});
|
|
2008
2419
|
var messageStream = (client, phone, onInbound) => resumableOrderedStream({
|
|
2009
2420
|
fetchMissed: (cursor) => catchUpEvents(client, cursor, isMessageEvent),
|
|
2010
|
-
|
|
2421
|
+
isCursorRejectedError: isCursorRejectedIMessageError,
|
|
2422
|
+
label: streamLabel("messages", phone),
|
|
2011
2423
|
processLive: (event) => toMessageItem(client, event, phone, String(event.sequence), onInbound),
|
|
2012
2424
|
processMissed: (event) => event.type === "catchup.complete" ? Promise.resolve(toCatchUpCompleteItem(event)) : toMessageItem(
|
|
2013
2425
|
client,
|
|
@@ -2020,7 +2432,8 @@ var messageStream = (client, phone, onInbound) => resumableOrderedStream({
|
|
|
2020
2432
|
});
|
|
2021
2433
|
var pollStream = (client, pollCache, phone) => resumableOrderedStream({
|
|
2022
2434
|
fetchMissed: (cursor) => catchUpEvents(client, cursor, isPollEvent),
|
|
2023
|
-
|
|
2435
|
+
isCursorRejectedError: isCursorRejectedIMessageError,
|
|
2436
|
+
label: streamLabel("polls", phone),
|
|
2024
2437
|
processLive: (event) => toPollItem(client, pollCache, event, phone, String(event.sequence)),
|
|
2025
2438
|
processMissed: (event) => event.type === "catchup.complete" ? Promise.resolve(toCatchUpCompleteItem(event)) : toPollItem(client, pollCache, event, phone, String(event.sequence)),
|
|
2026
2439
|
subscribeLive: (cursor) => withClose(client.polls.subscribeEvents(), cursor)
|
|
@@ -2050,6 +2463,12 @@ var INITIAL_THROTTLE_MS = 1e3;
|
|
|
2050
2463
|
var BACKOFF_FACTOR = 2;
|
|
2051
2464
|
var MAX_EDITS = 5;
|
|
2052
2465
|
var sendStreamText = async (remote, spaceId, content) => {
|
|
2466
|
+
if (content.format === "markdown") {
|
|
2467
|
+
throw unsupportedRemoteContent(
|
|
2468
|
+
"streamText",
|
|
2469
|
+
"markdown-formatted streams have no native iMessage delivery"
|
|
2470
|
+
);
|
|
2471
|
+
}
|
|
2053
2472
|
const chat = toChatGuid(spaceId);
|
|
2054
2473
|
let sent;
|
|
2055
2474
|
let full = "";
|
|
@@ -2089,6 +2508,7 @@ var sendStreamText = async (remote, spaceId, content) => {
|
|
|
2089
2508
|
return {
|
|
2090
2509
|
id: sent.guid,
|
|
2091
2510
|
content: asText(full),
|
|
2511
|
+
direction: "outbound",
|
|
2092
2512
|
space: { id: spaceId },
|
|
2093
2513
|
timestamp: sent.dateCreated
|
|
2094
2514
|
};
|
|
@@ -2121,9 +2541,9 @@ var send4 = async (remote, spaceId, content) => send3(remote, spaceId, content);
|
|
|
2121
2541
|
var sendStreamText2 = async (remote, spaceId, content) => sendStreamText(remote, spaceId, content);
|
|
2122
2542
|
var replyToMessage2 = async (remote, spaceId, msgId, content) => replyToMessage(remote, spaceId, msgId, content);
|
|
2123
2543
|
var editMessage2 = async (remote, spaceId, msgId, content) => editMessage(remote, spaceId, msgId, content);
|
|
2124
|
-
var reactToMessage2 = async (remote, spaceId, target, reaction) =>
|
|
2125
|
-
|
|
2126
|
-
|
|
2544
|
+
var reactToMessage2 = async (remote, spaceId, target, reaction) => reactToMessage(remote, spaceId, target, reaction);
|
|
2545
|
+
var unsendMessage2 = async (remote, spaceId, msgId) => unsendMessage(remote, spaceId, msgId);
|
|
2546
|
+
var unsendReaction2 = async (remote, spaceId, target, reaction) => unsendReaction(remote, spaceId, target, reaction);
|
|
2127
2547
|
var getMessage4 = async (remote, spaceId, msgId, phone) => getMessage3(remote, spaceId, msgId, phone);
|
|
2128
2548
|
|
|
2129
2549
|
// src/providers/imessage/remote/client.ts
|
|
@@ -2162,6 +2582,22 @@ var randomPhone = (clients) => {
|
|
|
2162
2582
|
|
|
2163
2583
|
// src/providers/imessage/index.ts
|
|
2164
2584
|
var isPollContent = (content) => content.type === "poll" || content.type === "poll_option";
|
|
2585
|
+
var cacheRemoteOutbound = (remote, space, record) => {
|
|
2586
|
+
if (!record) {
|
|
2587
|
+
return record;
|
|
2588
|
+
}
|
|
2589
|
+
cacheMessage(getMessageCache(remote), {
|
|
2590
|
+
...record,
|
|
2591
|
+
direction: record.direction ?? "outbound",
|
|
2592
|
+
space: {
|
|
2593
|
+
...record.space,
|
|
2594
|
+
id: record.space.id,
|
|
2595
|
+
phone: space.phone,
|
|
2596
|
+
type: space.type
|
|
2597
|
+
}
|
|
2598
|
+
});
|
|
2599
|
+
return record;
|
|
2600
|
+
};
|
|
2165
2601
|
var handleEdit = async (client, space, content) => {
|
|
2166
2602
|
if (isLocal(client)) {
|
|
2167
2603
|
throw UnsupportedError.action("edit", "iMessage (local mode)");
|
|
@@ -2176,6 +2612,30 @@ var handleEdit = async (client, space, content) => {
|
|
|
2176
2612
|
const remote = clientForPhone(client, space.phone);
|
|
2177
2613
|
await editMessage2(remote, space.id, content.target.id, content.content);
|
|
2178
2614
|
};
|
|
2615
|
+
var handleUnsend = async (client, space, content) => {
|
|
2616
|
+
if (isLocal(client)) {
|
|
2617
|
+
throw UnsupportedError.action("unsend", "iMessage (local mode)");
|
|
2618
|
+
}
|
|
2619
|
+
if (isPollContent(content.target.content)) {
|
|
2620
|
+
throw UnsupportedError.action(
|
|
2621
|
+
"unsend",
|
|
2622
|
+
"iMessage",
|
|
2623
|
+
"iMessage polls cannot be unsent"
|
|
2624
|
+
);
|
|
2625
|
+
}
|
|
2626
|
+
const remote = clientForPhone(client, space.phone);
|
|
2627
|
+
const targetContent = content.target.content;
|
|
2628
|
+
if (targetContent.type === "reaction") {
|
|
2629
|
+
await unsendReaction2(
|
|
2630
|
+
remote,
|
|
2631
|
+
space.id,
|
|
2632
|
+
targetContent.target,
|
|
2633
|
+
targetContent.emoji
|
|
2634
|
+
);
|
|
2635
|
+
return;
|
|
2636
|
+
}
|
|
2637
|
+
await unsendMessage2(remote, space.id, content.target.id);
|
|
2638
|
+
};
|
|
2179
2639
|
var handleStreamText = async (client, space, content) => {
|
|
2180
2640
|
if (isLocal(client)) {
|
|
2181
2641
|
throw UnsupportedError.action(
|
|
@@ -2185,7 +2645,11 @@ var handleStreamText = async (client, space, content) => {
|
|
|
2185
2645
|
);
|
|
2186
2646
|
}
|
|
2187
2647
|
const remote = clientForPhone(client, space.phone);
|
|
2188
|
-
return
|
|
2648
|
+
return cacheRemoteOutbound(
|
|
2649
|
+
remote,
|
|
2650
|
+
space,
|
|
2651
|
+
await sendStreamText2(remote, space.id, content)
|
|
2652
|
+
);
|
|
2189
2653
|
};
|
|
2190
2654
|
var handleBackground = async (client, space, content) => {
|
|
2191
2655
|
if (isLocal(client)) {
|
|
@@ -2207,7 +2671,11 @@ var handleCustomizedMiniApp = async (client, space, content) => {
|
|
|
2207
2671
|
);
|
|
2208
2672
|
}
|
|
2209
2673
|
const remote = clientForPhone(client, space.phone);
|
|
2210
|
-
return
|
|
2674
|
+
return cacheRemoteOutbound(
|
|
2675
|
+
remote,
|
|
2676
|
+
space,
|
|
2677
|
+
await sendCustomizedMiniApp2(remote, space.id, content)
|
|
2678
|
+
);
|
|
2211
2679
|
};
|
|
2212
2680
|
var handleRead = async (client, space) => {
|
|
2213
2681
|
if (isLocal(client)) {
|
|
@@ -2316,10 +2784,10 @@ var imessage = definePlatform("iMessage", {
|
|
|
2316
2784
|
space: {
|
|
2317
2785
|
schema: spaceSchema,
|
|
2318
2786
|
params: spaceParamsSchema,
|
|
2319
|
-
|
|
2787
|
+
create: async ({ input, client }) => {
|
|
2320
2788
|
if (isLocal(client)) {
|
|
2321
2789
|
throw UnsupportedError.action(
|
|
2322
|
-
"
|
|
2790
|
+
"space.create",
|
|
2323
2791
|
"iMessage (local mode)",
|
|
2324
2792
|
"local mode only supports replying to existing messages"
|
|
2325
2793
|
);
|
|
@@ -2330,18 +2798,52 @@ var imessage = definePlatform("iMessage", {
|
|
|
2330
2798
|
if (client.length === 0) {
|
|
2331
2799
|
throw new Error("No iMessage clients configured");
|
|
2332
2800
|
}
|
|
2333
|
-
const phone = isSharedMode(client) ? SHARED_PHONE : input.params?.phone ?? randomPhone(client);
|
|
2334
|
-
const remote = clientForPhone(client, phone);
|
|
2335
2801
|
const addresses = input.users.map((u) => u.id);
|
|
2336
|
-
if (
|
|
2802
|
+
if (isSharedMode(client)) {
|
|
2803
|
+
if (addresses.length > 1) {
|
|
2804
|
+
throw UnsupportedError.action(
|
|
2805
|
+
"space.create",
|
|
2806
|
+
"iMessage (shared mode)",
|
|
2807
|
+
"shared mode cannot create group chats \u2014 use a dedicated number, or space.get(chatGuid) for an existing group"
|
|
2808
|
+
);
|
|
2809
|
+
}
|
|
2337
2810
|
return {
|
|
2338
2811
|
id: dmChatGuid(addresses[0] ?? ""),
|
|
2339
2812
|
type: "dm",
|
|
2340
|
-
phone
|
|
2813
|
+
phone: SHARED_PHONE
|
|
2341
2814
|
};
|
|
2342
2815
|
}
|
|
2816
|
+
const phone = input.params?.phone ?? randomPhone(client);
|
|
2817
|
+
const remote = clientForPhone(client, phone);
|
|
2343
2818
|
const { chat } = await remote.chats.create(addresses);
|
|
2344
|
-
return {
|
|
2819
|
+
return {
|
|
2820
|
+
id: chat.guid,
|
|
2821
|
+
type: chat.isGroup ? "group" : "dm",
|
|
2822
|
+
phone
|
|
2823
|
+
};
|
|
2824
|
+
},
|
|
2825
|
+
get: async ({ input, client }) => {
|
|
2826
|
+
if (isLocal(client)) {
|
|
2827
|
+
throw UnsupportedError.action(
|
|
2828
|
+
"space.get",
|
|
2829
|
+
"iMessage (local mode)",
|
|
2830
|
+
"local mode only supports replying to existing messages"
|
|
2831
|
+
);
|
|
2832
|
+
}
|
|
2833
|
+
if (client.length === 0) {
|
|
2834
|
+
throw new Error("No iMessage clients configured");
|
|
2835
|
+
}
|
|
2836
|
+
const phone = isSharedMode(client) ? SHARED_PHONE : input.params?.phone ?? (client.length === 1 ? client[0]?.phone : void 0);
|
|
2837
|
+
if (!phone) {
|
|
2838
|
+
throw new Error(
|
|
2839
|
+
`iMessage space.get requires params.phone when multiple clients are configured. Available: ${availablePhones(client).join(", ")}`
|
|
2840
|
+
);
|
|
2841
|
+
}
|
|
2842
|
+
return {
|
|
2843
|
+
id: input.id,
|
|
2844
|
+
type: chatTypeFromGuid(input.id),
|
|
2845
|
+
phone
|
|
2846
|
+
};
|
|
2345
2847
|
},
|
|
2346
2848
|
actions: {
|
|
2347
2849
|
// Sugar: `space.background(input, opts?)` →
|
|
@@ -2382,11 +2884,15 @@ var imessage = definePlatform("iMessage", {
|
|
|
2382
2884
|
);
|
|
2383
2885
|
}
|
|
2384
2886
|
const remote2 = clientForPhone(client, space.phone);
|
|
2385
|
-
return
|
|
2887
|
+
return cacheRemoteOutbound(
|
|
2386
2888
|
remote2,
|
|
2387
|
-
space
|
|
2388
|
-
|
|
2389
|
-
|
|
2889
|
+
space,
|
|
2890
|
+
await replyToMessage2(
|
|
2891
|
+
remote2,
|
|
2892
|
+
space.id,
|
|
2893
|
+
content.target.id,
|
|
2894
|
+
content.content
|
|
2895
|
+
)
|
|
2390
2896
|
);
|
|
2391
2897
|
}
|
|
2392
2898
|
if (content.type === "reaction") {
|
|
@@ -2401,13 +2907,16 @@ var imessage = definePlatform("iMessage", {
|
|
|
2401
2907
|
);
|
|
2402
2908
|
}
|
|
2403
2909
|
const remote2 = clientForPhone(client, space.phone);
|
|
2404
|
-
|
|
2910
|
+
return cacheRemoteOutbound(
|
|
2405
2911
|
remote2,
|
|
2406
|
-
space
|
|
2407
|
-
|
|
2408
|
-
|
|
2912
|
+
space,
|
|
2913
|
+
await reactToMessage2(
|
|
2914
|
+
remote2,
|
|
2915
|
+
space.id,
|
|
2916
|
+
content.target,
|
|
2917
|
+
content.emoji
|
|
2918
|
+
)
|
|
2409
2919
|
);
|
|
2410
|
-
return;
|
|
2411
2920
|
}
|
|
2412
2921
|
if (content.type === "typing") {
|
|
2413
2922
|
await handleTyping(client, space, content.state);
|
|
@@ -2417,6 +2926,10 @@ var imessage = definePlatform("iMessage", {
|
|
|
2417
2926
|
await handleEdit(client, space, content);
|
|
2418
2927
|
return;
|
|
2419
2928
|
}
|
|
2929
|
+
if (content.type === "unsend") {
|
|
2930
|
+
await handleUnsend(client, space, content);
|
|
2931
|
+
return;
|
|
2932
|
+
}
|
|
2420
2933
|
if (content.type === "streamText") {
|
|
2421
2934
|
return await handleStreamText(client, space, content);
|
|
2422
2935
|
}
|
|
@@ -2443,7 +2956,11 @@ var imessage = definePlatform("iMessage", {
|
|
|
2443
2956
|
return await send2(client, space.id, content);
|
|
2444
2957
|
}
|
|
2445
2958
|
const remote = clientForPhone(client, space.phone);
|
|
2446
|
-
return
|
|
2959
|
+
return cacheRemoteOutbound(
|
|
2960
|
+
remote,
|
|
2961
|
+
space,
|
|
2962
|
+
await send4(remote, space.id, content)
|
|
2963
|
+
);
|
|
2447
2964
|
},
|
|
2448
2965
|
actions: {
|
|
2449
2966
|
getMessage: async ({ client }, space, messageId) => {
|