@photon-ai/chat-adapter-imessage 2.0.0 → 2.2.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/README.md +111 -12
- package/dist/index.d.ts +308 -16
- package/dist/index.js +604 -58
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,15 +1,115 @@
|
|
|
1
1
|
// src/adapter.ts
|
|
2
|
-
import { extractFiles, ValidationError as
|
|
2
|
+
import { extractFiles, ValidationError as ValidationError8 } from "@chat-adapter/shared";
|
|
3
3
|
import { NotImplementedError } from "chat";
|
|
4
4
|
import {
|
|
5
|
+
app as appContent,
|
|
6
|
+
markdown as markdownContent,
|
|
5
7
|
poll as pollContent,
|
|
6
8
|
Spectrum,
|
|
7
9
|
text as textContent
|
|
8
10
|
} from "spectrum-ts";
|
|
9
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
customizedMiniApp,
|
|
13
|
+
effect as effectContent,
|
|
14
|
+
imessage as imessage2
|
|
15
|
+
} from "spectrum-ts/providers/imessage";
|
|
10
16
|
|
|
11
|
-
// src/
|
|
17
|
+
// src/background.ts
|
|
12
18
|
import { ValidationError } from "@chat-adapter/shared";
|
|
19
|
+
import { lookup as lookupMimeType } from "mime-types";
|
|
20
|
+
import { background as backgroundContent } from "spectrum-ts/providers/imessage";
|
|
21
|
+
var IMAGE_MIME_PATTERN = /^image\//i;
|
|
22
|
+
async function toBytes(input) {
|
|
23
|
+
if (input instanceof Uint8Array) {
|
|
24
|
+
return { bytes: Buffer.from(input) };
|
|
25
|
+
}
|
|
26
|
+
if (input instanceof ArrayBuffer) {
|
|
27
|
+
return { bytes: Buffer.from(input) };
|
|
28
|
+
}
|
|
29
|
+
if (input instanceof Blob) {
|
|
30
|
+
return {
|
|
31
|
+
bytes: Buffer.from(await input.arrayBuffer()),
|
|
32
|
+
mimeHint: input.type || void 0
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
const data = input.data;
|
|
36
|
+
if (data === void 0) {
|
|
37
|
+
throw new ValidationError(
|
|
38
|
+
"imessage",
|
|
39
|
+
"Chat background must be a Uint8Array, ArrayBuffer, Blob, or FileUpload"
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
const nested = await toBytes(data);
|
|
43
|
+
return {
|
|
44
|
+
bytes: nested.bytes,
|
|
45
|
+
mimeHint: input.mimeType || nested.mimeHint,
|
|
46
|
+
nameHint: input.filename || nested.nameHint
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function toBackgroundUrl(input) {
|
|
50
|
+
if (input instanceof URL) {
|
|
51
|
+
return input;
|
|
52
|
+
}
|
|
53
|
+
if (typeof input !== "string") {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
let url;
|
|
57
|
+
try {
|
|
58
|
+
url = new URL(input);
|
|
59
|
+
} catch {
|
|
60
|
+
throw new ValidationError(
|
|
61
|
+
"imessage",
|
|
62
|
+
`Chat background string input must be an http(s) URL, got "${input}". Pass image bytes for local files.`
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
66
|
+
throw new ValidationError(
|
|
67
|
+
"imessage",
|
|
68
|
+
`Chat background URL must be http(s), got "${url.protocol}"`
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
return url;
|
|
72
|
+
}
|
|
73
|
+
function resolveBytesMime(hint, name) {
|
|
74
|
+
if (hint && IMAGE_MIME_PATTERN.test(hint)) {
|
|
75
|
+
return hint;
|
|
76
|
+
}
|
|
77
|
+
if (name) {
|
|
78
|
+
const inferred = lookupMimeType(name);
|
|
79
|
+
if (inferred && IMAGE_MIME_PATTERN.test(inferred)) {
|
|
80
|
+
return inferred;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
throw new ValidationError(
|
|
84
|
+
"imessage",
|
|
85
|
+
hint ? `Chat background requires an image/* MIME type, got "${hint}"` : 'Chat background requires an image/* MIME type \u2014 pass options.mimeType (e.g. "image/jpeg") or a name with an image extension'
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
async function resolveBackground(input, options = {}) {
|
|
89
|
+
if (input === "clear") {
|
|
90
|
+
return backgroundContent("clear");
|
|
91
|
+
}
|
|
92
|
+
if (options.mimeType && !IMAGE_MIME_PATTERN.test(options.mimeType)) {
|
|
93
|
+
throw new ValidationError(
|
|
94
|
+
"imessage",
|
|
95
|
+
`Chat background requires an image/* MIME type, got "${options.mimeType}"`
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
const url = toBackgroundUrl(input);
|
|
99
|
+
if (url) {
|
|
100
|
+
return backgroundContent(
|
|
101
|
+
url,
|
|
102
|
+
options.mimeType ? { mimeType: options.mimeType } : void 0
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
const { bytes, mimeHint, nameHint } = await toBytes(input);
|
|
106
|
+
const name = options.name ?? nameHint;
|
|
107
|
+
const mimeType = resolveBytesMime(options.mimeType ?? mimeHint, name);
|
|
108
|
+
return backgroundContent(bytes, { mimeType });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// src/config.ts
|
|
112
|
+
import { ValidationError as ValidationError2 } from "@chat-adapter/shared";
|
|
13
113
|
var SHARED_PHONE = "shared";
|
|
14
114
|
var URL_SCHEME_RE = /^[a-z][a-z0-9+.-]*:\/\//i;
|
|
15
115
|
function deriveAddress(serverUrl) {
|
|
@@ -45,18 +145,44 @@ function resolveSpectrumConfig(local, auth) {
|
|
|
45
145
|
}
|
|
46
146
|
};
|
|
47
147
|
}
|
|
48
|
-
throw new
|
|
148
|
+
throw new ValidationError2(
|
|
49
149
|
"imessage",
|
|
50
150
|
"Remote mode requires Spectrum Cloud credentials (projectId + projectSecret), explicit clients, or serverUrl + apiKey."
|
|
51
151
|
);
|
|
52
152
|
}
|
|
53
153
|
|
|
154
|
+
// src/effects.ts
|
|
155
|
+
import { ValidationError as ValidationError3 } from "@chat-adapter/shared";
|
|
156
|
+
import { imessage } from "spectrum-ts/providers/imessage";
|
|
157
|
+
var iMessageEffect = imessage.effect.message;
|
|
158
|
+
var EFFECT_IDS = new Set(Object.values(iMessageEffect));
|
|
159
|
+
var EFFECT_NAMES = Object.keys(iMessageEffect);
|
|
160
|
+
function resolveEffect(effect) {
|
|
161
|
+
if (effect in iMessageEffect) {
|
|
162
|
+
return iMessageEffect[effect];
|
|
163
|
+
}
|
|
164
|
+
if (EFFECT_IDS.has(effect)) {
|
|
165
|
+
return effect;
|
|
166
|
+
}
|
|
167
|
+
throw new ValidationError3(
|
|
168
|
+
"imessage",
|
|
169
|
+
`Unknown iMessage effect: "${effect}". Supported: ${EFFECT_NAMES.join(", ")}`
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
54
173
|
// src/internal/cache.ts
|
|
55
174
|
var DEFAULT_MAX_SPACES = 256;
|
|
56
175
|
var DEFAULT_MAX_MESSAGES = 1024;
|
|
57
176
|
var InboundCache = class {
|
|
58
177
|
spaces = /* @__PURE__ */ new Map();
|
|
59
178
|
messages = /* @__PURE__ */ new Map();
|
|
179
|
+
/**
|
|
180
|
+
* Reaction messages this session sent via `addReaction`, keyed by their
|
|
181
|
+
* target so `removeReaction` can `unsend()` them. spectrum-ts models a
|
|
182
|
+
* tapback as its own message, and the only handle to retract one is the
|
|
183
|
+
* `Message` that `react()` returns — there is no by-target removal API.
|
|
184
|
+
*/
|
|
185
|
+
reactions = /* @__PURE__ */ new Map();
|
|
60
186
|
maxSpaces;
|
|
61
187
|
maxMessages;
|
|
62
188
|
constructor(maxSpaces = DEFAULT_MAX_SPACES, maxMessages = DEFAULT_MAX_MESSAGES) {
|
|
@@ -87,7 +213,24 @@ var InboundCache = class {
|
|
|
87
213
|
getMessage(id) {
|
|
88
214
|
return this.messages.get(id);
|
|
89
215
|
}
|
|
216
|
+
/** Record the reaction message returned by `react()` for later removal. */
|
|
217
|
+
rememberReaction(targetMessageId, glyph, reaction) {
|
|
218
|
+
this.reactions.set(reactionKey(targetMessageId, glyph), reaction);
|
|
219
|
+
evict(this.reactions, this.maxMessages);
|
|
220
|
+
}
|
|
221
|
+
/** Look up (and forget) a tracked reaction so it can be `unsend()`-ed once. */
|
|
222
|
+
takeReaction(targetMessageId, glyph) {
|
|
223
|
+
const key = reactionKey(targetMessageId, glyph);
|
|
224
|
+
const reaction = this.reactions.get(key);
|
|
225
|
+
if (reaction) {
|
|
226
|
+
this.reactions.delete(key);
|
|
227
|
+
}
|
|
228
|
+
return reaction;
|
|
229
|
+
}
|
|
90
230
|
};
|
|
231
|
+
function reactionKey(targetMessageId, glyph) {
|
|
232
|
+
return `${targetMessageId}::${glyph}`;
|
|
233
|
+
}
|
|
91
234
|
function evict(map, max) {
|
|
92
235
|
if (map.size <= max) {
|
|
93
236
|
return;
|
|
@@ -168,21 +311,21 @@ var MessagePump = class {
|
|
|
168
311
|
import { Message, parseMarkdown } from "chat";
|
|
169
312
|
|
|
170
313
|
// src/internal/thread.ts
|
|
171
|
-
import { ValidationError as
|
|
314
|
+
import { ValidationError as ValidationError4 } from "@chat-adapter/shared";
|
|
172
315
|
var THREAD_PREFIX = "imessage:";
|
|
173
316
|
function encodeThreadId(platformData) {
|
|
174
317
|
return `${THREAD_PREFIX}${platformData.chatGuid}`;
|
|
175
318
|
}
|
|
176
319
|
function decodeThreadId(threadId) {
|
|
177
320
|
if (!threadId.startsWith(THREAD_PREFIX)) {
|
|
178
|
-
throw new
|
|
321
|
+
throw new ValidationError4(
|
|
179
322
|
"imessage",
|
|
180
323
|
`Invalid iMessage thread ID: ${threadId}`
|
|
181
324
|
);
|
|
182
325
|
}
|
|
183
326
|
const chatGuid = threadId.slice(THREAD_PREFIX.length);
|
|
184
327
|
if (!chatGuid) {
|
|
185
|
-
throw new
|
|
328
|
+
throw new ValidationError4(
|
|
186
329
|
"imessage",
|
|
187
330
|
`Invalid iMessage thread ID: ${threadId} (empty chat GUID)`
|
|
188
331
|
);
|
|
@@ -192,11 +335,6 @@ function decodeThreadId(threadId) {
|
|
|
192
335
|
function isDMChatGuid(chatGuid) {
|
|
193
336
|
return chatGuid.includes(";-;");
|
|
194
337
|
}
|
|
195
|
-
var DM_SEPARATOR = ";-;";
|
|
196
|
-
function dmAddressFromChatGuid(chatGuid) {
|
|
197
|
-
const idx = chatGuid.indexOf(DM_SEPARATOR);
|
|
198
|
-
return idx === -1 ? "" : chatGuid.slice(idx + DM_SEPARATOR.length);
|
|
199
|
-
}
|
|
200
338
|
|
|
201
339
|
// src/internal/inbound.ts
|
|
202
340
|
function buildChatMessageFromFields(fields) {
|
|
@@ -322,8 +460,8 @@ function titleKey(chatGuid, title) {
|
|
|
322
460
|
}
|
|
323
461
|
|
|
324
462
|
// src/internal/outbound.ts
|
|
325
|
-
import { ValidationError as
|
|
326
|
-
import { lookup as
|
|
463
|
+
import { ValidationError as ValidationError5 } from "@chat-adapter/shared";
|
|
464
|
+
import { lookup as lookupMimeType2 } from "mime-types";
|
|
327
465
|
import { attachment } from "spectrum-ts";
|
|
328
466
|
var EMOJI_GLYPHS = {
|
|
329
467
|
heart: "\u2764\uFE0F",
|
|
@@ -341,7 +479,7 @@ function emojiToGlyph(emoji) {
|
|
|
341
479
|
const name = typeof emoji === "string" ? emoji : emoji.name;
|
|
342
480
|
const glyph = EMOJI_GLYPHS[name];
|
|
343
481
|
if (!glyph) {
|
|
344
|
-
throw new
|
|
482
|
+
throw new ValidationError5(
|
|
345
483
|
"imessage",
|
|
346
484
|
`Unsupported iMessage tapback: "${name}". Supported: heart, thumbs_up, thumbs_down, laugh, emphasize, question`
|
|
347
485
|
);
|
|
@@ -359,7 +497,7 @@ async function fileToAttachment(file) {
|
|
|
359
497
|
buffer = Buffer.from(data);
|
|
360
498
|
}
|
|
361
499
|
const name = file.filename || "attachment";
|
|
362
|
-
const mimeType = file.mimeType ||
|
|
500
|
+
const mimeType = file.mimeType || lookupMimeType2(name) || "application/octet-stream";
|
|
363
501
|
return attachment(buffer, { name, mimeType });
|
|
364
502
|
}
|
|
365
503
|
|
|
@@ -416,7 +554,8 @@ import {
|
|
|
416
554
|
isParagraphNode,
|
|
417
555
|
isStrongNode,
|
|
418
556
|
isTextNode,
|
|
419
|
-
parseMarkdown as parseMarkdown2
|
|
557
|
+
parseMarkdown as parseMarkdown2,
|
|
558
|
+
stringifyMarkdown
|
|
420
559
|
} from "chat";
|
|
421
560
|
var iMessageFormatConverter = class extends BaseFormatConverter {
|
|
422
561
|
/**
|
|
@@ -436,6 +575,30 @@ var iMessageFormatConverter = class extends BaseFormatConverter {
|
|
|
436
575
|
toAst(text) {
|
|
437
576
|
return parseMarkdown2(text);
|
|
438
577
|
}
|
|
578
|
+
/**
|
|
579
|
+
* Decide how a postable message should reach spectrum-ts.
|
|
580
|
+
*
|
|
581
|
+
* Markdown-typed inputs -- `{ markdown }` and `{ ast }` -- carry CommonMark
|
|
582
|
+
* the caller wants styled, so their source is preserved verbatim and flagged
|
|
583
|
+
* `markdown: true`; the adapter sends it via spectrum's `markdown()` builder,
|
|
584
|
+
* which renders bold/italic/links/lists as native iMessage styled text.
|
|
585
|
+
*
|
|
586
|
+
* Everything else is pass-through-as-is by contract -- a plain `string` or
|
|
587
|
+
* `{ raw }` must not have stray `*`/`_` reinterpreted as formatting, and
|
|
588
|
+
* cards fall back to plain text -- so those are rendered to plain text and
|
|
589
|
+
* flagged `markdown: false`.
|
|
590
|
+
*/
|
|
591
|
+
renderPostableContent(message) {
|
|
592
|
+
if (message && typeof message === "object") {
|
|
593
|
+
if ("markdown" in message && typeof message.markdown === "string") {
|
|
594
|
+
return { body: message.markdown, markdown: true };
|
|
595
|
+
}
|
|
596
|
+
if ("ast" in message && message.ast) {
|
|
597
|
+
return { body: stringifyMarkdown(message.ast), markdown: true };
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
return { body: this.renderPostable(message), markdown: false };
|
|
601
|
+
}
|
|
439
602
|
nodeToPlainText(node) {
|
|
440
603
|
if (isParagraphNode(node)) {
|
|
441
604
|
return getNodeChildren(node).map((child) => this.nodeToPlainText(child)).join("");
|
|
@@ -483,6 +646,169 @@ var iMessageFormatConverter = class extends BaseFormatConverter {
|
|
|
483
646
|
}
|
|
484
647
|
};
|
|
485
648
|
|
|
649
|
+
// src/miniapp.ts
|
|
650
|
+
import { ValidationError as ValidationError6 } from "@chat-adapter/shared";
|
|
651
|
+
async function toImageBytes(image) {
|
|
652
|
+
if (image instanceof Uint8Array) {
|
|
653
|
+
return new Uint8Array(image);
|
|
654
|
+
}
|
|
655
|
+
if (image instanceof ArrayBuffer) {
|
|
656
|
+
return new Uint8Array(image);
|
|
657
|
+
}
|
|
658
|
+
if (image instanceof Blob) {
|
|
659
|
+
return new Uint8Array(await image.arrayBuffer());
|
|
660
|
+
}
|
|
661
|
+
const data = image.data;
|
|
662
|
+
if (data === void 0) {
|
|
663
|
+
throw new ValidationError6(
|
|
664
|
+
"imessage",
|
|
665
|
+
"Mini-app card image must be a Uint8Array, ArrayBuffer, Blob, or FileUpload"
|
|
666
|
+
);
|
|
667
|
+
}
|
|
668
|
+
return toImageBytes(data);
|
|
669
|
+
}
|
|
670
|
+
function requireField(value, field) {
|
|
671
|
+
if (!value || value.trim().length === 0) {
|
|
672
|
+
throw new ValidationError6(
|
|
673
|
+
"imessage",
|
|
674
|
+
`Mini-app card requires a non-empty "${field}"`
|
|
675
|
+
);
|
|
676
|
+
}
|
|
677
|
+
return value;
|
|
678
|
+
}
|
|
679
|
+
async function resolveMiniApp(card) {
|
|
680
|
+
const url = typeof card.url === "string" ? card.url : card.url.toString();
|
|
681
|
+
try {
|
|
682
|
+
new URL(url);
|
|
683
|
+
} catch {
|
|
684
|
+
throw new ValidationError6(
|
|
685
|
+
"imessage",
|
|
686
|
+
`Mini-app card has an invalid url: "${url}"`
|
|
687
|
+
);
|
|
688
|
+
}
|
|
689
|
+
const layout = card.layout ?? {};
|
|
690
|
+
const image = layout.image ? await toImageBytes(layout.image) : void 0;
|
|
691
|
+
return {
|
|
692
|
+
appName: requireField(card.appName, "appName"),
|
|
693
|
+
teamId: requireField(card.teamId, "teamId"),
|
|
694
|
+
extensionBundleId: requireField(
|
|
695
|
+
card.extensionBundleId,
|
|
696
|
+
"extensionBundleId"
|
|
697
|
+
),
|
|
698
|
+
url,
|
|
699
|
+
...card.appStoreId === void 0 ? {} : { appStoreId: card.appStoreId },
|
|
700
|
+
layout: {
|
|
701
|
+
caption: layout.caption,
|
|
702
|
+
subcaption: layout.subcaption,
|
|
703
|
+
trailingCaption: layout.trailingCaption,
|
|
704
|
+
trailingSubcaption: layout.trailingSubcaption,
|
|
705
|
+
imageTitle: layout.imageTitle,
|
|
706
|
+
imageSubtitle: layout.imageSubtitle,
|
|
707
|
+
summary: layout.summary,
|
|
708
|
+
...image ? { image } : {}
|
|
709
|
+
}
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
function isAppUrl(input) {
|
|
713
|
+
return typeof input === "string" || typeof input === "function" || typeof input === "object" && input !== null && typeof input.then === "function";
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// src/voice.ts
|
|
717
|
+
import { ValidationError as ValidationError7 } from "@chat-adapter/shared";
|
|
718
|
+
import { lookup as lookupMimeType3 } from "mime-types";
|
|
719
|
+
import { voice as voiceContent } from "spectrum-ts";
|
|
720
|
+
var AUDIO_MIME_PATTERN = /^audio\//i;
|
|
721
|
+
async function toBytes2(input) {
|
|
722
|
+
if (input instanceof Uint8Array) {
|
|
723
|
+
return { bytes: Buffer.from(input) };
|
|
724
|
+
}
|
|
725
|
+
if (input instanceof ArrayBuffer) {
|
|
726
|
+
return { bytes: Buffer.from(input) };
|
|
727
|
+
}
|
|
728
|
+
if (input instanceof Blob) {
|
|
729
|
+
return {
|
|
730
|
+
bytes: Buffer.from(await input.arrayBuffer()),
|
|
731
|
+
mimeHint: input.type || void 0
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
const data = input.data;
|
|
735
|
+
if (data === void 0) {
|
|
736
|
+
throw new ValidationError7(
|
|
737
|
+
"imessage",
|
|
738
|
+
"Voice message must be a Uint8Array, ArrayBuffer, Blob, or FileUpload"
|
|
739
|
+
);
|
|
740
|
+
}
|
|
741
|
+
const nested = await toBytes2(data);
|
|
742
|
+
return {
|
|
743
|
+
bytes: nested.bytes,
|
|
744
|
+
mimeHint: input.mimeType || nested.mimeHint,
|
|
745
|
+
nameHint: input.filename || nested.nameHint
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
function toVoiceUrl(input) {
|
|
749
|
+
if (input instanceof URL) {
|
|
750
|
+
return input;
|
|
751
|
+
}
|
|
752
|
+
if (typeof input !== "string") {
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
let url;
|
|
756
|
+
try {
|
|
757
|
+
url = new URL(input);
|
|
758
|
+
} catch {
|
|
759
|
+
throw new ValidationError7(
|
|
760
|
+
"imessage",
|
|
761
|
+
`Voice message string input must be an http(s) URL, got "${input}". Pass audio bytes for local files.`
|
|
762
|
+
);
|
|
763
|
+
}
|
|
764
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
765
|
+
throw new ValidationError7(
|
|
766
|
+
"imessage",
|
|
767
|
+
`Voice message URL must be http(s), got "${url.protocol}"`
|
|
768
|
+
);
|
|
769
|
+
}
|
|
770
|
+
return url;
|
|
771
|
+
}
|
|
772
|
+
function resolveBytesMime2(hint, name) {
|
|
773
|
+
if (hint && AUDIO_MIME_PATTERN.test(hint)) {
|
|
774
|
+
return hint;
|
|
775
|
+
}
|
|
776
|
+
if (name) {
|
|
777
|
+
const inferred = lookupMimeType3(name);
|
|
778
|
+
if (inferred && AUDIO_MIME_PATTERN.test(inferred)) {
|
|
779
|
+
return inferred;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
throw new ValidationError7(
|
|
783
|
+
"imessage",
|
|
784
|
+
hint ? `Voice message requires an audio/* MIME type, got "${hint}"` : 'Voice message requires an audio/* MIME type \u2014 pass options.mimeType (e.g. "audio/mp4") or a name with an audio extension'
|
|
785
|
+
);
|
|
786
|
+
}
|
|
787
|
+
async function resolveVoice(input, options = {}) {
|
|
788
|
+
if (options.mimeType && !AUDIO_MIME_PATTERN.test(options.mimeType)) {
|
|
789
|
+
throw new ValidationError7(
|
|
790
|
+
"imessage",
|
|
791
|
+
`Voice message requires an audio/* MIME type, got "${options.mimeType}"`
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
const url = toVoiceUrl(input);
|
|
795
|
+
if (url) {
|
|
796
|
+
return voiceContent(url, {
|
|
797
|
+
...options.mimeType ? { mimeType: options.mimeType } : {},
|
|
798
|
+
...options.name ? { name: options.name } : {},
|
|
799
|
+
...options.duration === void 0 ? {} : { duration: options.duration }
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
const { bytes, mimeHint, nameHint } = await toBytes2(input);
|
|
803
|
+
const name = options.name ?? nameHint;
|
|
804
|
+
const mimeType = resolveBytesMime2(options.mimeType ?? mimeHint, name);
|
|
805
|
+
return voiceContent(bytes, {
|
|
806
|
+
mimeType,
|
|
807
|
+
...name ? { name } : {},
|
|
808
|
+
...options.duration === void 0 ? {} : { duration: options.duration }
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
|
|
486
812
|
// src/adapter.ts
|
|
487
813
|
var TYPING_DURATION_MS = 3e3;
|
|
488
814
|
var iMessageAdapter = class {
|
|
@@ -507,7 +833,7 @@ var iMessageAdapter = class {
|
|
|
507
833
|
pump = null;
|
|
508
834
|
constructor(config) {
|
|
509
835
|
if (config.local && process.platform !== "darwin") {
|
|
510
|
-
throw new
|
|
836
|
+
throw new ValidationError8(
|
|
511
837
|
"imessage",
|
|
512
838
|
"iMessage adapter local mode requires macOS. Current platform: " + process.platform
|
|
513
839
|
);
|
|
@@ -537,7 +863,7 @@ var iMessageAdapter = class {
|
|
|
537
863
|
serverUrl: this.serverUrl
|
|
538
864
|
}
|
|
539
865
|
);
|
|
540
|
-
const providers = [
|
|
866
|
+
const providers = [imessage2.config(providerConfig)];
|
|
541
867
|
this.app = projectId && projectSecret ? await Spectrum({ providers, projectId, projectSecret }) : await Spectrum({ providers });
|
|
542
868
|
this.pump = new MessagePump(
|
|
543
869
|
() => {
|
|
@@ -566,9 +892,9 @@ var iMessageAdapter = class {
|
|
|
566
892
|
* Handle a Spectrum Cloud webhook delivery (signed JSON `messages` event).
|
|
567
893
|
*
|
|
568
894
|
* Verifies the `X-Spectrum-Signature` HMAC, then routes the message into the
|
|
569
|
-
* Chat SDK.
|
|
570
|
-
*
|
|
571
|
-
*
|
|
895
|
+
* Chat SDK. A delivered thread has no live spectrum-ts `Space`, but the
|
|
896
|
+
* adapter rebuilds one from the chat GUID on demand (see `resolveSpace`), so
|
|
897
|
+
* replying works directly from a webhook delivery.
|
|
572
898
|
*
|
|
573
899
|
* @see https://photon.codes/docs/webhooks/overview
|
|
574
900
|
*/
|
|
@@ -617,26 +943,152 @@ var iMessageAdapter = class {
|
|
|
617
943
|
this.routeWebhookMessage(payload, options);
|
|
618
944
|
return new Response(null, { status: 200 });
|
|
619
945
|
}
|
|
946
|
+
/**
|
|
947
|
+
* Build the spectrum content for an outbound message. Markdown-typed inputs
|
|
948
|
+
* are sent via `markdown()` so remote iMessage renders them as native styled
|
|
949
|
+
* text; raw/string/card inputs stay plain `text()`. Returns the rendered
|
|
950
|
+
* `body` too so callers can skip an empty send.
|
|
951
|
+
*/
|
|
952
|
+
toSpectrumContent(message) {
|
|
953
|
+
const { body, markdown } = this.formatConverter.renderPostableContent(message);
|
|
954
|
+
return {
|
|
955
|
+
body,
|
|
956
|
+
content: markdown ? markdownContent(body) : textContent(body)
|
|
957
|
+
};
|
|
958
|
+
}
|
|
620
959
|
async postMessage(threadId, message) {
|
|
621
960
|
const space = await this.requireSpace(threadId, "postMessage");
|
|
622
|
-
const body = this.
|
|
961
|
+
const { body, content } = this.toSpectrumContent(message);
|
|
623
962
|
const files = extractFiles(message);
|
|
624
963
|
let first;
|
|
625
964
|
if (body && body.trim().length > 0) {
|
|
626
|
-
first = await space.send(
|
|
965
|
+
first = await space.send(content) ?? first;
|
|
627
966
|
}
|
|
628
967
|
for (const file of files) {
|
|
629
968
|
const sent = await space.send(await fileToAttachment(file)) ?? void 0;
|
|
630
969
|
first ??= sent;
|
|
631
970
|
}
|
|
632
971
|
if (!first) {
|
|
633
|
-
throw new
|
|
972
|
+
throw new ValidationError8(
|
|
634
973
|
"imessage",
|
|
635
974
|
"postMessage requires non-empty text or at least one attachment"
|
|
636
975
|
);
|
|
637
976
|
}
|
|
638
977
|
return { id: first.id, threadId, raw: first };
|
|
639
978
|
}
|
|
979
|
+
/**
|
|
980
|
+
* Send a message with an iMessage expressive-send effect — a bubble effect
|
|
981
|
+
* (`slam`, `loud`, `gentle`, `invisible`) or a full-screen effect
|
|
982
|
+
* (`confetti`, `fireworks`, `balloons`, `heart`, `lasers`, `celebration`,
|
|
983
|
+
* `sparkles`, `spotlight`, `echo`). Not part of the Chat SDK `Adapter`
|
|
984
|
+
* interface — exposed as an adapter-specific extra (e.g. celebratory confetti
|
|
985
|
+
* on task completion). Remote only.
|
|
986
|
+
*
|
|
987
|
+
* The `effect` argument accepts a friendly name (`"confetti"`) or a value from
|
|
988
|
+
* the re-exported `iMessageEffect` map. Effects attach to text content only,
|
|
989
|
+
* so this requires non-empty text.
|
|
990
|
+
*/
|
|
991
|
+
async sendEffect(threadId, message, effect) {
|
|
992
|
+
if (this.local) {
|
|
993
|
+
throw new NotImplementedError(
|
|
994
|
+
"sendEffect is not supported in local mode",
|
|
995
|
+
"sendEffect"
|
|
996
|
+
);
|
|
997
|
+
}
|
|
998
|
+
const space = await this.requireSpace(threadId, "sendEffect");
|
|
999
|
+
const { body, content } = this.toSpectrumContent(message);
|
|
1000
|
+
if (!body || body.trim().length === 0) {
|
|
1001
|
+
throw new ValidationError8(
|
|
1002
|
+
"imessage",
|
|
1003
|
+
"sendEffect requires non-empty text content"
|
|
1004
|
+
);
|
|
1005
|
+
}
|
|
1006
|
+
const effectId = resolveEffect(effect);
|
|
1007
|
+
const sent = await space.send(effectContent(content, effectId));
|
|
1008
|
+
if (!sent) {
|
|
1009
|
+
throw new ValidationError8(
|
|
1010
|
+
"imessage",
|
|
1011
|
+
"sendEffect could not send the message"
|
|
1012
|
+
);
|
|
1013
|
+
}
|
|
1014
|
+
return { id: sent.id, threadId, raw: sent };
|
|
1015
|
+
}
|
|
1016
|
+
async sendMiniApp(threadId, input) {
|
|
1017
|
+
if (this.local) {
|
|
1018
|
+
throw new NotImplementedError(
|
|
1019
|
+
"sendMiniApp is not supported in local mode",
|
|
1020
|
+
"sendMiniApp"
|
|
1021
|
+
);
|
|
1022
|
+
}
|
|
1023
|
+
const space = await this.requireSpace(threadId, "sendMiniApp");
|
|
1024
|
+
const content = isAppUrl(input) ? appContent(input) : customizedMiniApp(await resolveMiniApp(input));
|
|
1025
|
+
const sent = await space.send(content);
|
|
1026
|
+
if (!sent) {
|
|
1027
|
+
throw new ValidationError8(
|
|
1028
|
+
"imessage",
|
|
1029
|
+
"sendMiniApp could not send the card"
|
|
1030
|
+
);
|
|
1031
|
+
}
|
|
1032
|
+
return { id: sent.id, threadId, raw: sent };
|
|
1033
|
+
}
|
|
1034
|
+
/**
|
|
1035
|
+
* Send a native iMessage voice note — a real, playable waveform bubble (the
|
|
1036
|
+
* message renders with `isAudioMessage`), not an audio file dropped in as an
|
|
1037
|
+
* attachment. A natural fit for TTS-capable bots that reply with speech. Not
|
|
1038
|
+
* part of the Chat SDK `Adapter` interface — exposed as an adapter-specific
|
|
1039
|
+
* extra. Remote only.
|
|
1040
|
+
*
|
|
1041
|
+
* The `input` is either in-memory audio bytes (`Uint8Array` / `Buffer` /
|
|
1042
|
+
* `ArrayBuffer`, a `Blob`, or a Chat SDK `FileUpload`) or an `http(s)` URL (a
|
|
1043
|
+
* `URL` or a string) that is fetched at send time. Audio bytes need an
|
|
1044
|
+
* `audio/*` MIME type — supply `options.mimeType` (e.g. `"audio/mp4"`) or an
|
|
1045
|
+
* `options.name` with an audio extension when it can't be inferred.
|
|
1046
|
+
*/
|
|
1047
|
+
async sendVoice(threadId, input, options) {
|
|
1048
|
+
if (this.local) {
|
|
1049
|
+
throw new NotImplementedError(
|
|
1050
|
+
"sendVoice is not supported in local mode",
|
|
1051
|
+
"sendVoice"
|
|
1052
|
+
);
|
|
1053
|
+
}
|
|
1054
|
+
const space = await this.requireSpace(threadId, "sendVoice");
|
|
1055
|
+
const content = await resolveVoice(input, options);
|
|
1056
|
+
const sent = await space.send(content);
|
|
1057
|
+
if (!sent) {
|
|
1058
|
+
throw new ValidationError8(
|
|
1059
|
+
"imessage",
|
|
1060
|
+
"sendVoice could not send the voice message"
|
|
1061
|
+
);
|
|
1062
|
+
}
|
|
1063
|
+
return { id: sent.id, threadId, raw: sent };
|
|
1064
|
+
}
|
|
1065
|
+
/**
|
|
1066
|
+
* Set or clear the chat background — the wallpaper behind a conversation, an
|
|
1067
|
+
* iMessage-only touch with no analog on the plain-text competitors. Not part
|
|
1068
|
+
* of the Chat SDK `Adapter` interface — exposed as an adapter-specific extra.
|
|
1069
|
+
* Remote only.
|
|
1070
|
+
*
|
|
1071
|
+
* The `input` is either the literal `"clear"` (to remove the current
|
|
1072
|
+
* background), in-memory image bytes (`Uint8Array` / `Buffer` / `ArrayBuffer`,
|
|
1073
|
+
* a `Blob`, or a Chat SDK `FileUpload`), or an `http(s)` URL (a `URL` or a
|
|
1074
|
+
* string) that is fetched at send time. Image bytes need an `image/*` MIME
|
|
1075
|
+
* type — supply `options.mimeType` (e.g. `"image/jpeg"`) or an `options.name`
|
|
1076
|
+
* with an image extension when it can't be inferred.
|
|
1077
|
+
*
|
|
1078
|
+
* Fire-and-forget: iMessage acknowledges the control signal without returning
|
|
1079
|
+
* a message, so this resolves to `void` rather than a {@link RawMessage}.
|
|
1080
|
+
*/
|
|
1081
|
+
async setBackground(threadId, input, options) {
|
|
1082
|
+
if (this.local) {
|
|
1083
|
+
throw new NotImplementedError(
|
|
1084
|
+
"setBackground is not supported in local mode",
|
|
1085
|
+
"setBackground"
|
|
1086
|
+
);
|
|
1087
|
+
}
|
|
1088
|
+
const space = await this.requireSpace(threadId, "setBackground");
|
|
1089
|
+
const content = await resolveBackground(input, options);
|
|
1090
|
+
await space.send(content);
|
|
1091
|
+
}
|
|
640
1092
|
async editMessage(threadId, messageId, message) {
|
|
641
1093
|
if (this.local) {
|
|
642
1094
|
throw new NotImplementedError(
|
|
@@ -651,21 +1103,42 @@ var iMessageAdapter = class {
|
|
|
651
1103
|
"editMessage"
|
|
652
1104
|
);
|
|
653
1105
|
}
|
|
654
|
-
await target.edit(
|
|
655
|
-
textContent(this.formatConverter.renderPostable(message))
|
|
656
|
-
);
|
|
1106
|
+
await target.edit(this.toSpectrumContent(message).content);
|
|
657
1107
|
return { id: messageId, threadId, raw: target };
|
|
658
1108
|
}
|
|
659
|
-
async deleteMessage(
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
1109
|
+
async deleteMessage(threadId, messageId) {
|
|
1110
|
+
if (this.local) {
|
|
1111
|
+
throw new NotImplementedError(
|
|
1112
|
+
"deleteMessage is not supported in local mode",
|
|
1113
|
+
"deleteMessage"
|
|
1114
|
+
);
|
|
1115
|
+
}
|
|
1116
|
+
const target = await this.resolveMessage(threadId, messageId);
|
|
1117
|
+
if (!target) {
|
|
1118
|
+
throw new NotImplementedError(
|
|
1119
|
+
"deleteMessage requires the target message to have been sent or received in this session",
|
|
1120
|
+
"deleteMessage"
|
|
1121
|
+
);
|
|
1122
|
+
}
|
|
1123
|
+
await target.unsend();
|
|
664
1124
|
}
|
|
665
1125
|
parseMessage(raw) {
|
|
666
1126
|
const message = raw;
|
|
667
1127
|
return buildChatMessage(message, message.space);
|
|
668
1128
|
}
|
|
1129
|
+
/**
|
|
1130
|
+
* Fetch a single message by id. spectrum-ts can resolve a message by id
|
|
1131
|
+
* (from the inbound cache or the provider's by-id lookup) even though it has
|
|
1132
|
+
* no paginated history API, so single-message reads work where
|
|
1133
|
+
* `fetchMessages` cannot. Returns `null` when the message can't be resolved.
|
|
1134
|
+
*/
|
|
1135
|
+
async fetchMessage(threadId, messageId) {
|
|
1136
|
+
const target = await this.resolveMessage(threadId, messageId);
|
|
1137
|
+
if (!target) {
|
|
1138
|
+
return null;
|
|
1139
|
+
}
|
|
1140
|
+
return this.parseMessage(target);
|
|
1141
|
+
}
|
|
669
1142
|
async fetchMessages(_threadId, _options) {
|
|
670
1143
|
throw new NotImplementedError(
|
|
671
1144
|
"fetchMessages (message history) is not supported by spectrum-ts",
|
|
@@ -696,13 +1169,27 @@ var iMessageAdapter = class {
|
|
|
696
1169
|
"addReaction"
|
|
697
1170
|
);
|
|
698
1171
|
}
|
|
699
|
-
await target.react(glyph);
|
|
1172
|
+
const reaction = await target.react(glyph);
|
|
1173
|
+
if (reaction) {
|
|
1174
|
+
this.cache.rememberReaction(messageId, glyph, reaction);
|
|
1175
|
+
}
|
|
700
1176
|
}
|
|
701
|
-
async removeReaction(_threadId,
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
1177
|
+
async removeReaction(_threadId, messageId, emoji) {
|
|
1178
|
+
if (this.local) {
|
|
1179
|
+
throw new NotImplementedError(
|
|
1180
|
+
"removeReaction is not supported in local mode",
|
|
1181
|
+
"removeReaction"
|
|
1182
|
+
);
|
|
1183
|
+
}
|
|
1184
|
+
const glyph = emojiToGlyph(emoji);
|
|
1185
|
+
const reaction = this.cache.takeReaction(messageId, glyph);
|
|
1186
|
+
if (!reaction) {
|
|
1187
|
+
throw new NotImplementedError(
|
|
1188
|
+
"removeReaction requires the reaction to have been added via addReaction in this session",
|
|
1189
|
+
"removeReaction"
|
|
1190
|
+
);
|
|
1191
|
+
}
|
|
1192
|
+
await reaction.unsend();
|
|
706
1193
|
}
|
|
707
1194
|
async startTyping(threadId, _status) {
|
|
708
1195
|
if (this.local) {
|
|
@@ -718,6 +1205,44 @@ var iMessageAdapter = class {
|
|
|
718
1205
|
});
|
|
719
1206
|
}, TYPING_DURATION_MS);
|
|
720
1207
|
}
|
|
1208
|
+
/**
|
|
1209
|
+
* Cold-start a DM with a phone number / handle. spectrum-ts resolves (or
|
|
1210
|
+
* creates) the 1:1 conversation from the participant via `space.create`, so
|
|
1211
|
+
* the bot can message a user it has never received from. Returns the encoded
|
|
1212
|
+
* thread id, ready to pass to `postMessage`.
|
|
1213
|
+
*/
|
|
1214
|
+
async openDM(userId) {
|
|
1215
|
+
if (!this.app) {
|
|
1216
|
+
throw new NotImplementedError(
|
|
1217
|
+
"openDM requires the adapter to be initialized",
|
|
1218
|
+
"openDM"
|
|
1219
|
+
);
|
|
1220
|
+
}
|
|
1221
|
+
const space = await this.platformSpaces().create(userId);
|
|
1222
|
+
this.cache.rememberSpace(space);
|
|
1223
|
+
return encodeThreadId({ chatGuid: space.id });
|
|
1224
|
+
}
|
|
1225
|
+
/**
|
|
1226
|
+
* Mark a received message (and the conversation up to it) as read, surfacing
|
|
1227
|
+
* a read receipt where iMessage supports one. Remote only; not part of the
|
|
1228
|
+
* Chat SDK `Adapter` interface — exposed as an adapter-specific extra.
|
|
1229
|
+
*/
|
|
1230
|
+
async markRead(threadId, messageId) {
|
|
1231
|
+
if (this.local) {
|
|
1232
|
+
throw new NotImplementedError(
|
|
1233
|
+
"markRead is not supported in local mode",
|
|
1234
|
+
"markRead"
|
|
1235
|
+
);
|
|
1236
|
+
}
|
|
1237
|
+
const target = await this.resolveMessage(threadId, messageId);
|
|
1238
|
+
if (!target) {
|
|
1239
|
+
throw new NotImplementedError(
|
|
1240
|
+
"markRead requires the target message to have been received in this session",
|
|
1241
|
+
"markRead"
|
|
1242
|
+
);
|
|
1243
|
+
}
|
|
1244
|
+
await target.read();
|
|
1245
|
+
}
|
|
721
1246
|
async openModal(triggerId, modal, contextId) {
|
|
722
1247
|
if (this.local) {
|
|
723
1248
|
throw new NotImplementedError(
|
|
@@ -729,14 +1254,14 @@ var iMessageAdapter = class {
|
|
|
729
1254
|
(c) => c.type === "select"
|
|
730
1255
|
);
|
|
731
1256
|
if (!select) {
|
|
732
|
-
throw new
|
|
1257
|
+
throw new ValidationError8(
|
|
733
1258
|
"imessage",
|
|
734
1259
|
"openModal requires at least one Select child \u2014 iMessage modals map to native polls"
|
|
735
1260
|
);
|
|
736
1261
|
}
|
|
737
1262
|
const labels = select.options.map((o) => o.label);
|
|
738
1263
|
if (labels.length < 2 || labels.length > 10) {
|
|
739
|
-
throw new
|
|
1264
|
+
throw new ValidationError8(
|
|
740
1265
|
"imessage",
|
|
741
1266
|
`iMessage polls require between 2 and 10 options, received ${labels.length}`
|
|
742
1267
|
);
|
|
@@ -896,11 +1421,12 @@ var iMessageAdapter = class {
|
|
|
896
1421
|
*
|
|
897
1422
|
* Prefers a live `Space` cached from the inbound stream (correct sending
|
|
898
1423
|
* line, no extra round-trip). On a miss — e.g. a webhook-only deployment, or
|
|
899
|
-
* a cold send — it
|
|
900
|
-
* `imessage(app).space(
|
|
901
|
-
*
|
|
902
|
-
*
|
|
903
|
-
* Returns `undefined` when no Space can be
|
|
1424
|
+
* a cold send — it rebuilds the Space from the chat GUID via
|
|
1425
|
+
* `imessage(app).space.get(chatGuid)`, which works for DMs and groups alike.
|
|
1426
|
+
* The rebuild can still fail when several iMessage lines are configured and
|
|
1427
|
+
* spectrum-ts cannot infer which line the chat belongs to (`space.get`
|
|
1428
|
+
* requires `params.phone` there). Returns `undefined` when no Space can be
|
|
1429
|
+
* obtained.
|
|
904
1430
|
*/
|
|
905
1431
|
async resolveSpace(threadId) {
|
|
906
1432
|
const { chatGuid } = decodeThreadId(threadId);
|
|
@@ -908,23 +1434,37 @@ var iMessageAdapter = class {
|
|
|
908
1434
|
if (cached) {
|
|
909
1435
|
return cached;
|
|
910
1436
|
}
|
|
911
|
-
if (!this.app
|
|
1437
|
+
if (!this.app) {
|
|
912
1438
|
return;
|
|
913
1439
|
}
|
|
914
|
-
|
|
915
|
-
|
|
1440
|
+
try {
|
|
1441
|
+
const space = await this.platformSpaces().get(chatGuid);
|
|
1442
|
+
this.cache.rememberSpace(space);
|
|
1443
|
+
return space;
|
|
1444
|
+
} catch (error) {
|
|
1445
|
+
this.logger.debug("Could not rebuild Space from chat GUID", {
|
|
1446
|
+
chatGuid,
|
|
1447
|
+
error: String(error)
|
|
1448
|
+
});
|
|
916
1449
|
return;
|
|
917
1450
|
}
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
1451
|
+
}
|
|
1452
|
+
/**
|
|
1453
|
+
* The iMessage provider's Space namespace (`get` / `create`). `HasProvider`
|
|
1454
|
+
* over the default provider tuple won't narrow to `true`, so `imessage(app)`
|
|
1455
|
+
* types as `never` — cast to the slice of the instance we use.
|
|
1456
|
+
*/
|
|
1457
|
+
platformSpaces() {
|
|
1458
|
+
if (!this.app) {
|
|
1459
|
+
throw new Error("Adapter not initialized");
|
|
1460
|
+
}
|
|
1461
|
+
return imessage2(this.app).space;
|
|
922
1462
|
}
|
|
923
1463
|
async requireSpace(threadId, action) {
|
|
924
1464
|
const space = await this.resolveSpace(threadId);
|
|
925
1465
|
if (!space) {
|
|
926
1466
|
throw new NotImplementedError(
|
|
927
|
-
`${action}
|
|
1467
|
+
`${action} could not resolve this thread. With multiple iMessage lines configured, spectrum-ts needs the chat's sending line to rebuild an unseen thread. Respond within a received message's thread instead.`,
|
|
928
1468
|
action
|
|
929
1469
|
);
|
|
930
1470
|
}
|
|
@@ -950,7 +1490,7 @@ function toClientArray(clients) {
|
|
|
950
1490
|
}
|
|
951
1491
|
|
|
952
1492
|
// src/factory.ts
|
|
953
|
-
import { ValidationError as
|
|
1493
|
+
import { ValidationError as ValidationError9 } from "@chat-adapter/shared";
|
|
954
1494
|
import { ConsoleLogger } from "chat";
|
|
955
1495
|
function resolveLocalMode(explicit, hasRemoteCreds) {
|
|
956
1496
|
if (explicit !== void 0) {
|
|
@@ -970,13 +1510,13 @@ function assertRemoteCredentials(creds) {
|
|
|
970
1510
|
return;
|
|
971
1511
|
}
|
|
972
1512
|
if (!creds.hasServerUrl) {
|
|
973
|
-
throw new
|
|
1513
|
+
throw new ValidationError9(
|
|
974
1514
|
"imessage",
|
|
975
1515
|
"serverUrl is required when local is false. Set IMESSAGE_SERVER_URL (or use IMESSAGE_PROJECT_ID/IMESSAGE_PROJECT_SECRET for Spectrum Cloud), or provide it in config."
|
|
976
1516
|
);
|
|
977
1517
|
}
|
|
978
1518
|
if (!creds.hasApiKey) {
|
|
979
|
-
throw new
|
|
1519
|
+
throw new ValidationError9(
|
|
980
1520
|
"imessage",
|
|
981
1521
|
"apiKey is required when local is false. Set IMESSAGE_API_KEY or provide it in config."
|
|
982
1522
|
);
|
|
@@ -1016,6 +1556,12 @@ export {
|
|
|
1016
1556
|
createiMessageAdapter,
|
|
1017
1557
|
deriveAddress,
|
|
1018
1558
|
iMessageAdapter,
|
|
1019
|
-
|
|
1559
|
+
iMessageEffect,
|
|
1560
|
+
iMessageFormatConverter,
|
|
1561
|
+
isAppUrl,
|
|
1562
|
+
resolveBackground,
|
|
1563
|
+
resolveEffect,
|
|
1564
|
+
resolveMiniApp,
|
|
1565
|
+
resolveVoice
|
|
1020
1566
|
};
|
|
1021
1567
|
//# sourceMappingURL=index.js.map
|