@photon-ai/chat-adapter-imessage 2.1.0 → 2.2.1
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 +107 -8
- package/dist/index.d.ts +318 -19
- package/dist/index.js +654 -65
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -238,15 +238,23 @@ IMESSAGE_WEBHOOK_SECRET=whsec_... # per-webhook signing secret
|
|
|
238
238
|
|---------|-----------|
|
|
239
239
|
| Mentions | DMs only |
|
|
240
240
|
| DMs | Yes |
|
|
241
|
+
| Open DM (cold-start) | Yes (`openDM`) |
|
|
241
242
|
| File uploads | Yes (send) |
|
|
242
243
|
| Reactions (add) | Remote only |
|
|
243
|
-
| Reactions (remove) |
|
|
244
|
+
| Reactions (remove) | Remote only (session-added tapbacks) |
|
|
244
245
|
| Message editing | Remote only |
|
|
246
|
+
| Message delete | Remote only (iMessage unsend window) |
|
|
247
|
+
| Mark read | Remote only (`markRead`) |
|
|
245
248
|
| Typing indicator | Remote only |
|
|
249
|
+
| Message effects | Remote only (`sendEffect`) |
|
|
250
|
+
| Mini-app cards | Remote only (`sendMiniApp`) |
|
|
251
|
+
| Voice messages | Remote only (`sendVoice`) |
|
|
252
|
+
| Chat background | Remote only (`setBackground`) |
|
|
246
253
|
| Modals | Limited (Remote only) |
|
|
254
|
+
| Fetch single message | Yes (`fetchMessage`) |
|
|
247
255
|
| Message history | No |
|
|
248
256
|
| Thread/chat info | No |
|
|
249
|
-
| Cards |
|
|
257
|
+
| Cards | Mini-app cards only (`sendMiniApp`) |
|
|
250
258
|
| Streaming | No |
|
|
251
259
|
| Ephemeral messages | No |
|
|
252
260
|
| Webhooks | Yes (remote — Spectrum Cloud delivery) |
|
|
@@ -312,14 +320,100 @@ iMessage uses tapbacks instead of emoji reactions. The adapter maps standard emo
|
|
|
312
320
|
| `emphasize` / `exclamation` | Emphasize |
|
|
313
321
|
| `question` | Question |
|
|
314
322
|
|
|
323
|
+
## Message effects
|
|
324
|
+
|
|
325
|
+
iMessage expressive-send effects animate a message when it arrives. The adapter exposes them through `sendEffect(threadId, message, effect)` — an adapter-specific extra (there is no first-class Chat SDK slot for effects). It sends the text with the effect attached and returns the sent message. Remote only; local mode throws `NotImplementedError`.
|
|
326
|
+
|
|
327
|
+
```typescript
|
|
328
|
+
import { createiMessageAdapter, iMessageEffect } from "@photon-ai/chat-adapter-imessage";
|
|
329
|
+
|
|
330
|
+
const adapter = createiMessageAdapter({ local: false });
|
|
331
|
+
|
|
332
|
+
bot.onNewMention(async (thread) => {
|
|
333
|
+
// Friendly name…
|
|
334
|
+
await adapter.sendEffect(thread.id, "🎉 Task complete!", "confetti");
|
|
335
|
+
// …or the typed constant:
|
|
336
|
+
await adapter.sendEffect(thread.id, "🎉 Task complete!", iMessageEffect.confetti);
|
|
337
|
+
});
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
The `effect` argument accepts a friendly name or a value from the re-exported `iMessageEffect` map. Full-screen effects: `confetti`, `fireworks`, `balloons`, `heart`, `lasers`, `celebration`, `sparkles`, `spotlight`, `echo`. Bubble effects: `slam`, `loud`, `gentle`, `invisible` (invisible ink). Effects attach to text only, so `sendEffect` requires non-empty text content; an unknown effect throws a `ValidationError`.
|
|
341
|
+
|
|
342
|
+
## Mini-app cards
|
|
343
|
+
|
|
344
|
+
Mini-app cards are native `MSMessageExtension` balloons — a rich card with a tap-through URL, the closest iMessage gets to a Slack-style rich card rather than a bare link. The adapter exposes them through `sendMiniApp(threadId, card)` — an adapter-specific extra (there is no first-class Chat SDK slot for cards). Remote only; local mode throws `NotImplementedError`.
|
|
345
|
+
|
|
346
|
+
`sendMiniApp` takes **either** a bare URL **or** a fully-specified card.
|
|
347
|
+
|
|
348
|
+
### Just a URL (`app(url)`)
|
|
349
|
+
|
|
350
|
+
The lightweight form: pass a URL string and iMessage renders it as a mini-app — no extension identifiers required. You can also pass a `Promise<string>` or a thunk (`() => string | Promise<string>`), so the link can be minted at send time (e.g. a signed URL).
|
|
351
|
+
|
|
352
|
+
```typescript
|
|
353
|
+
import { createiMessageAdapter } from "@photon-ai/chat-adapter-imessage";
|
|
354
|
+
|
|
355
|
+
const adapter = createiMessageAdapter({ local: false });
|
|
356
|
+
|
|
357
|
+
bot.onNewMention(async (thread) => {
|
|
358
|
+
await adapter.sendMiniApp(thread.id, "https://example.com/menu");
|
|
359
|
+
|
|
360
|
+
// …or compute the URL lazily at send time:
|
|
361
|
+
await adapter.sendMiniApp(thread.id, async () => mintSignedLink(thread.id));
|
|
362
|
+
});
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### A full card (`customizedMiniApp`)
|
|
366
|
+
|
|
367
|
+
Pass an object to control the bubble's image, captions, and the exact iMessage extension that opens on tap.
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
await adapter.sendMiniApp(thread.id, {
|
|
371
|
+
appName: "Poll Kit",
|
|
372
|
+
teamId: "TEAM123",
|
|
373
|
+
extensionBundleId: "com.example.pollkit.MessagesExtension",
|
|
374
|
+
url: "https://example.com/poll/42",
|
|
375
|
+
appStoreId: 1_234_567, // optional — for recipients without the extension
|
|
376
|
+
layout: {
|
|
377
|
+
caption: "Pizza night?",
|
|
378
|
+
subcaption: "Tap to vote",
|
|
379
|
+
imageTitle: "Friday",
|
|
380
|
+
image: pngBytes, // Uint8Array | Buffer | ArrayBuffer | Blob | FileUpload
|
|
381
|
+
summary: "Vote on Friday's dinner",
|
|
382
|
+
},
|
|
383
|
+
});
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
`appName`, `teamId`, and `extensionBundleId` identify the iMessage extension that opens (receiving `url`) when the recipient taps the card; the server builds the matching `MSMessageExtensionBalloonPlugin` id from `teamId` + `extensionBundleId`. Every `layout` field is optional. The `url` accepts a string or `URL` and is validated; a missing required field or an invalid URL throws a `ValidationError`.
|
|
387
|
+
|
|
388
|
+
## Chat background
|
|
389
|
+
|
|
390
|
+
iMessage lets a conversation carry its own wallpaper — a touch with no analog on the plain-text competitors. The adapter exposes it through `setBackground(threadId, input, options?)` — an adapter-specific extra (there is no first-class Chat SDK slot for it). It is fire-and-forget: iMessage acknowledges the control signal without returning a message, so the call resolves to `void`. Remote only; local mode throws `NotImplementedError`.
|
|
391
|
+
|
|
392
|
+
```typescript
|
|
393
|
+
import { readFile } from "node:fs/promises";
|
|
394
|
+
|
|
395
|
+
// From image bytes (Uint8Array | Buffer | ArrayBuffer | Blob | FileUpload).
|
|
396
|
+
await adapter.setBackground(thread.id, await readFile("./wallpaper.jpg"), {
|
|
397
|
+
mimeType: "image/jpeg",
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
// From an http(s) URL, fetched at send time.
|
|
401
|
+
await adapter.setBackground(thread.id, "https://example.com/wallpaper.jpg");
|
|
402
|
+
|
|
403
|
+
// Remove the current background.
|
|
404
|
+
await adapter.setBackground(thread.id, "clear");
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
Pass the literal `"clear"` to remove the current background, in-memory image bytes, or an `http(s)` URL (a `URL` or a string) that spectrum-ts fetches at send time. Image bytes need an `image/*` MIME type — supply `options.mimeType` (e.g. `"image/jpeg"`) or an `options.name` with an image extension when it can't be inferred. Local file-path strings are rejected — read the file into bytes and pass those instead. A non-image MIME type, an unresolvable MIME type, or a non-`http(s)` string throws a `ValidationError`.
|
|
408
|
+
|
|
315
409
|
## Limitations
|
|
316
410
|
|
|
317
411
|
- **Cold sends need a resolvable line.** The adapter rebuilds a thread — DM or group — from its chat GUID via spectrum-ts's `space.get`, so it can send, react, edit, and show typing even into a thread it hasn't seen this session, including a [webhook](#webhooks) delivery. With **multiple iMessage lines** configured, spectrum-ts cannot infer which line an unseen chat belongs to, so cold sends there throw `NotImplementedError` — respond within a received message's thread instead.
|
|
318
|
-
- **No message history.** `fetchMessages` is not supported — spectrum-ts exposes no paginated history API.
|
|
412
|
+
- **No message history.** `fetchMessages` is not supported — spectrum-ts exposes no paginated history API. Single messages resolve via `fetchMessage` (from the session cache or spectrum-ts's by-id lookup).
|
|
319
413
|
- **No thread/chat info.** `fetchThread` is not supported.
|
|
320
|
-
- **
|
|
321
|
-
- **Local mode** supports sending (including cold sends by chat GUID) and
|
|
322
|
-
- **Formatting.** iMessage
|
|
414
|
+
- **Session-scoped delete & reaction removal.** `deleteMessage` unsends a message resolved from this session (subject to iMessage's ~2-minute unsend window); `removeReaction` retracts a tapback only if it was added via `addReaction` earlier in this session — spectrum-ts exposes no by-target reaction lookup.
|
|
415
|
+
- **Local mode** supports sending (including cold sends by chat GUID), receiving, and opening DMs, but not reactions, typing, editing, deleting, marking read, effects, modals, history, or thread info.
|
|
416
|
+
- **Formatting.** Markdown-typed content (`{ markdown }` or `{ ast }`) renders as native iMessage styled text on remote — bold, italics, links, and lists — via spectrum-ts's `markdown()` builder. Plain strings and `{ raw }` are sent as-is (never reinterpreted as Markdown). Inbound messages always surface as plain text.
|
|
323
417
|
- **Platform.** Local mode requires macOS. Cloud and self-host run anywhere.
|
|
324
418
|
- **Cards.** iMessage has no structured card layouts.
|
|
325
419
|
|
|
@@ -330,7 +424,8 @@ This version re-platforms the adapter onto **spectrum-ts**. If you are upgrading
|
|
|
330
424
|
- **Dependency** — replaces `@photon-ai/imessage-kit` + `@photon-ai/advanced-imessage-kit` with `spectrum-ts`.
|
|
331
425
|
- **`IMESSAGE_SERVER_URL` is now a gRPC `host:port`** (self-host), not an `https://` / Socket.IO URL.
|
|
332
426
|
- **New cloud path** — set `IMESSAGE_PROJECT_ID` + `IMESSAGE_PROJECT_SECRET` for Spectrum Cloud.
|
|
333
|
-
- **
|
|
427
|
+
- **Unsupported capabilities** (throw `NotImplementedError`): `fetchMessages` and `fetchThread` — spectrum-ts exposes no paginated history or chat-info API. Local `fetchMessages` (previously supported) is also removed. Cold `postMessage` works for DMs and groups alike (rebuilt from the chat GUID — see [Limitations](#limitations)).
|
|
428
|
+
- **Newly supported on spectrum-ts v8**: `deleteMessage` (unsend), `removeReaction` (retract a session-added tapback), `openDM` (cold-start a DM from a handle), `fetchMessage` (single message by id), and `markRead` — all remote-only where noted in [Features](#features).
|
|
334
429
|
- **`adapter.sdk` → `adapter.app`** — the adapter now exposes the underlying `SpectrumInstance` as `adapter.app` (null until `initialize()`).
|
|
335
430
|
|
|
336
431
|
## Troubleshooting
|
|
@@ -349,10 +444,14 @@ This version re-platforms the adapter onto **spectrum-ts**. If you are upgrading
|
|
|
349
444
|
- Verify **Full Disk Access** is granted to your terminal or application.
|
|
350
445
|
- Check that iMessage is signed in and working.
|
|
351
446
|
|
|
352
|
-
### `NotImplementedError` from `fetchMessages` / `fetchThread`
|
|
447
|
+
### `NotImplementedError` from `fetchMessages` / `fetchThread`
|
|
353
448
|
|
|
354
449
|
- These are not supported by spectrum-ts. See [Limitations](#limitations).
|
|
355
450
|
|
|
451
|
+
### `NotImplementedError` from `deleteMessage` / `removeReaction` / `markRead`
|
|
452
|
+
|
|
453
|
+
- These are remote-only and session-scoped. `deleteMessage` and `markRead` need the target message to have been seen this session (and delete is bound by iMessage's ~2-minute unsend window); `removeReaction` needs the tapback to have been added via `addReaction` this session. In local mode they throw. See [Limitations](#limitations).
|
|
454
|
+
|
|
356
455
|
## License
|
|
357
456
|
|
|
358
457
|
MIT
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,54 @@
|
|
|
1
|
-
import { Logger, Adapter, ChatInstance, WebhookOptions, AdapterPostableMessage, RawMessage, Message, FetchOptions, FetchResult, ThreadInfo, EmojiValue, ModalElement, FormattedContent, BaseFormatConverter, Root } from 'chat';
|
|
2
|
-
import { SpectrumInstance } from 'spectrum-ts';
|
|
1
|
+
import { FileUpload, Logger, Adapter, ChatInstance, WebhookOptions, AdapterPostableMessage, RawMessage, Message, FetchOptions, FetchResult, ThreadInfo, EmojiValue, ModalElement, FormattedContent, BaseFormatConverter, Root } from 'chat';
|
|
2
|
+
import { ContentBuilder, AppUrl, SpectrumInstance } from 'spectrum-ts';
|
|
3
|
+
export { AppUrl } from 'spectrum-ts';
|
|
4
|
+
import { IMessageMessageEffect, CustomizedMiniAppInput } from 'spectrum-ts/providers/imessage';
|
|
5
|
+
export { CustomizedMiniAppInput, IMessageMessageEffect } from 'spectrum-ts/providers/imessage';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Image bytes for a chat background. Accepts raw bytes (`Uint8Array` / `Buffer`
|
|
9
|
+
* / `ArrayBuffer`), a `Blob`, or a Chat SDK `FileUpload` — whatever your image
|
|
10
|
+
* pipeline hands back gets normalized to the bytes spectrum-ts expects.
|
|
11
|
+
*/
|
|
12
|
+
type BackgroundBytes = Uint8Array | ArrayBuffer | Blob | FileUpload;
|
|
13
|
+
/**
|
|
14
|
+
* A chat-background source. Either:
|
|
15
|
+
*
|
|
16
|
+
* - the literal `"clear"` sentinel, to remove the current background;
|
|
17
|
+
* - in-memory {@link BackgroundBytes};
|
|
18
|
+
* - an `http(s)` URL (a `URL` or a string) that spectrum-ts fetches at send
|
|
19
|
+
* time. Local file paths aren't accepted — read the file into bytes and pass
|
|
20
|
+
* those instead.
|
|
21
|
+
*/
|
|
22
|
+
type BackgroundInput = "clear" | BackgroundBytes | URL | string;
|
|
23
|
+
/** Optional chat-background metadata. */
|
|
24
|
+
interface BackgroundOptions {
|
|
25
|
+
/**
|
|
26
|
+
* MIME type of the image (`image/*`). Required for raw bytes when the `name`
|
|
27
|
+
* carries no image extension; inferred from the URL / name otherwise.
|
|
28
|
+
*/
|
|
29
|
+
mimeType?: string;
|
|
30
|
+
/** File name used to infer the MIME type from its extension. */
|
|
31
|
+
name?: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Validate and normalize a chat-background source into the spectrum-ts
|
|
35
|
+
* `background()` content builder. The `"clear"` sentinel removes the current
|
|
36
|
+
* background; URL sources are fetched by spectrum-ts at send time; byte sources
|
|
37
|
+
* are copied to a detached `Buffer` and tagged with a resolved `image/*` MIME
|
|
38
|
+
* type. The builder is a fire-and-forget control signal — remote and
|
|
39
|
+
* iMessage-only.
|
|
40
|
+
*/
|
|
41
|
+
declare function resolveBackground(input: BackgroundInput, options?: BackgroundOptions): Promise<ContentBuilder>;
|
|
3
42
|
|
|
4
43
|
/** Thread ID components for iMessage */
|
|
5
44
|
interface iMessageThreadId {
|
|
6
45
|
/** Chat GUID (e.g., "iMessage;-;+1234567890") */
|
|
7
46
|
chatGuid: string;
|
|
47
|
+
/**
|
|
48
|
+
* Sending line, when known — encoded in the thread ID so it survives a
|
|
49
|
+
* cold-cache reply invocation where the Space must be rebuilt.
|
|
50
|
+
*/
|
|
51
|
+
phone?: string;
|
|
8
52
|
}
|
|
9
53
|
/**
|
|
10
54
|
* Explicit self-hosted iMessage client entry, passed straight through to
|
|
@@ -69,6 +113,136 @@ interface CreateiMessageAdapterOptions {
|
|
|
69
113
|
*/
|
|
70
114
|
declare function deriveAddress(serverUrl: string): string;
|
|
71
115
|
|
|
116
|
+
/**
|
|
117
|
+
* iMessage expressive-send effects, keyed by friendly name. Bubble effects
|
|
118
|
+
* (`slam`, `loud`, `gentle`, `invisible`) animate the message bubble; the rest
|
|
119
|
+
* are full-screen effects (`confetti`, `fireworks`, `balloons`, `heart`,
|
|
120
|
+
* `lasers`, `celebration`, `sparkles`, `spotlight`, `echo`).
|
|
121
|
+
*
|
|
122
|
+
* Re-exported from spectrum-ts so callers can reference effects by name without
|
|
123
|
+
* reaching into the provider package — e.g. `iMessageEffect.confetti`.
|
|
124
|
+
*/
|
|
125
|
+
declare const iMessageEffect: {
|
|
126
|
+
readonly slam: "com.apple.MobileSMS.expressivesend.impact";
|
|
127
|
+
readonly loud: "com.apple.MobileSMS.expressivesend.loud";
|
|
128
|
+
readonly gentle: "com.apple.MobileSMS.expressivesend.gentle";
|
|
129
|
+
readonly invisible: "com.apple.MobileSMS.expressivesend.invisibleink";
|
|
130
|
+
readonly confetti: "com.apple.messages.effect.CKConfettiEffect";
|
|
131
|
+
readonly fireworks: "com.apple.messages.effect.CKFireworksEffect";
|
|
132
|
+
readonly balloons: "com.apple.messages.effect.CKBalloonEffect";
|
|
133
|
+
readonly heart: "com.apple.messages.effect.CKHeartEffect";
|
|
134
|
+
readonly lasers: "com.apple.messages.effect.CKLasersEffect";
|
|
135
|
+
readonly celebration: "com.apple.messages.effect.CKHappyBirthdayEffect";
|
|
136
|
+
readonly sparkles: "com.apple.messages.effect.CKSparklesEffect";
|
|
137
|
+
readonly spotlight: "com.apple.messages.effect.CKSpotlightEffect";
|
|
138
|
+
readonly echo: "com.apple.messages.effect.CKEchoEffect";
|
|
139
|
+
};
|
|
140
|
+
/** Accepted effect names (`"confetti"`, `"fireworks"`, …). */
|
|
141
|
+
type iMessageEffectName = keyof typeof iMessageEffect;
|
|
142
|
+
/**
|
|
143
|
+
* Resolve an effect argument to a spectrum-ts effect id. Accepts either a
|
|
144
|
+
* friendly name (`"confetti"`) or the raw effect id
|
|
145
|
+
* (`iMessageEffect.confetti`), so both styles work. Throws a `ValidationError`
|
|
146
|
+
* on an unknown effect.
|
|
147
|
+
*/
|
|
148
|
+
declare function resolveEffect(effect: IMessageMessageEffect | iMessageEffectName): IMessageMessageEffect;
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Image bytes for a mini-app card's inline image. Accepts raw bytes
|
|
152
|
+
* (`Uint8Array` / `Buffer` / `ArrayBuffer`), a `Blob`, or a Chat SDK
|
|
153
|
+
* `FileUpload` — whatever you already have on hand gets converted to the
|
|
154
|
+
* `Uint8Array` spectrum-ts expects.
|
|
155
|
+
*/
|
|
156
|
+
type MiniAppImage = Uint8Array | ArrayBuffer | Blob | FileUpload;
|
|
157
|
+
/**
|
|
158
|
+
* Layout of a mini-app card — everything the recipient sees in the bubble.
|
|
159
|
+
* Every field is optional; supply the ones that make sense for your card. The
|
|
160
|
+
* `image` renders as the card's artwork, with `imageTitle` / `imageSubtitle`
|
|
161
|
+
* overlaid; the caption fields fill the text rows, and `summary` is the
|
|
162
|
+
* fallback text shown where the mini-app can't render.
|
|
163
|
+
*/
|
|
164
|
+
interface MiniAppCardLayout {
|
|
165
|
+
caption?: string;
|
|
166
|
+
image?: MiniAppImage;
|
|
167
|
+
imageSubtitle?: string;
|
|
168
|
+
imageTitle?: string;
|
|
169
|
+
subcaption?: string;
|
|
170
|
+
summary?: string;
|
|
171
|
+
trailingCaption?: string;
|
|
172
|
+
trailingSubcaption?: string;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* A native iMessage mini-app card (an `MSMessageExtension` balloon). The
|
|
176
|
+
* `appName`, `teamId`, and `extensionBundleId` identify the iMessage extension
|
|
177
|
+
* that opens when the recipient taps the card, receiving `url`; `appStoreId`
|
|
178
|
+
* optionally points recipients without the extension installed at its App
|
|
179
|
+
* Store entry.
|
|
180
|
+
*/
|
|
181
|
+
interface MiniAppCard {
|
|
182
|
+
/** Display name of the iMessage extension. */
|
|
183
|
+
appName: string;
|
|
184
|
+
/** Optional App Store id, for recipients without the extension installed. */
|
|
185
|
+
appStoreId?: number;
|
|
186
|
+
/** Bundle id of the `MSMessageExtension` that receives the tap. */
|
|
187
|
+
extensionBundleId: string;
|
|
188
|
+
/** What the recipient sees in the bubble. */
|
|
189
|
+
layout?: MiniAppCardLayout;
|
|
190
|
+
/** Apple Developer team id that signs the extension. */
|
|
191
|
+
teamId: string;
|
|
192
|
+
/** URL handed to the extension when the card is tapped. */
|
|
193
|
+
url: string | URL;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Validate and normalize a {@link MiniAppCard} into the
|
|
197
|
+
* {@link CustomizedMiniAppInput} spectrum-ts's `customizedMiniApp()` builder
|
|
198
|
+
* expects: required identifiers are checked non-empty, `url` is normalized to a
|
|
199
|
+
* validated string, and the layout image (if any) is decoded to bytes.
|
|
200
|
+
*/
|
|
201
|
+
declare function resolveMiniApp(card: MiniAppCard): Promise<CustomizedMiniAppInput>;
|
|
202
|
+
/**
|
|
203
|
+
* Distinguish the lightweight `app(url)` form from a fully-specified
|
|
204
|
+
* {@link MiniAppCard}. An {@link AppUrl} is a string, a `Promise<string>`, or a
|
|
205
|
+
* thunk (`() => string | Promise<string>`) — the mini-app card is a plain
|
|
206
|
+
* object with named fields, so it never matches any of those shapes.
|
|
207
|
+
*/
|
|
208
|
+
declare function isAppUrl(input: MiniAppCard | AppUrl): input is AppUrl;
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Audio bytes for a voice message. Accepts raw bytes (`Uint8Array` / `Buffer` /
|
|
212
|
+
* `ArrayBuffer`), a `Blob`, or a Chat SDK `FileUpload` — whatever your TTS
|
|
213
|
+
* pipeline hands back gets normalized to the bytes spectrum-ts expects.
|
|
214
|
+
*/
|
|
215
|
+
type VoiceBytes = Uint8Array | ArrayBuffer | Blob | FileUpload;
|
|
216
|
+
/**
|
|
217
|
+
* A voice message source: in-memory {@link VoiceBytes}, or an `http(s)` URL (a
|
|
218
|
+
* `URL` or a string) that spectrum-ts fetches at send time. Local file paths
|
|
219
|
+
* aren't accepted — read the file into bytes and pass those instead.
|
|
220
|
+
*/
|
|
221
|
+
type VoiceInput = VoiceBytes | URL | string;
|
|
222
|
+
/** Optional voice-message metadata. */
|
|
223
|
+
interface VoiceOptions {
|
|
224
|
+
/**
|
|
225
|
+
* Playback duration in seconds, surfaced on the waveform bubble. Optional —
|
|
226
|
+
* iMessage derives the waveform from the audio itself when omitted.
|
|
227
|
+
*/
|
|
228
|
+
duration?: number;
|
|
229
|
+
/**
|
|
230
|
+
* MIME type of the audio (`audio/*`). Required for raw bytes when the `name`
|
|
231
|
+
* carries no audio extension; inferred from the URL / name otherwise.
|
|
232
|
+
*/
|
|
233
|
+
mimeType?: string;
|
|
234
|
+
/** File name handed to spectrum-ts (also used to infer the MIME type). */
|
|
235
|
+
name?: string;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Validate and normalize a voice source into the spectrum-ts `voice()` content
|
|
239
|
+
* builder. URL sources are fetched by spectrum-ts at send time; byte sources
|
|
240
|
+
* are copied to a detached `Buffer` and tagged with a resolved `audio/*` MIME
|
|
241
|
+
* type. The builder is sent as a native iMessage voice note (a waveform
|
|
242
|
+
* bubble), not an audio-file attachment.
|
|
243
|
+
*/
|
|
244
|
+
declare function resolveVoice(input: VoiceInput, options?: VoiceOptions): Promise<ContentBuilder>;
|
|
245
|
+
|
|
72
246
|
declare class iMessageAdapter implements Adapter {
|
|
73
247
|
readonly name = "imessage";
|
|
74
248
|
readonly userName: string;
|
|
@@ -80,8 +254,10 @@ declare class iMessageAdapter implements Adapter {
|
|
|
80
254
|
readonly clients?: IMessageClientEntry[];
|
|
81
255
|
readonly phone?: string;
|
|
82
256
|
readonly webhookSecret?: string;
|
|
83
|
-
/** The spectrum-ts instance — null until `initialize()` runs. */
|
|
257
|
+
/** The spectrum-ts instance — null until `initialize()` or `ensureApp()` runs. */
|
|
84
258
|
app: SpectrumInstance | null;
|
|
259
|
+
/** In-flight app build, so concurrent callers share one construction. */
|
|
260
|
+
private appBuild;
|
|
85
261
|
private chat;
|
|
86
262
|
private readonly logger;
|
|
87
263
|
private readonly formatConverter;
|
|
@@ -91,6 +267,13 @@ declare class iMessageAdapter implements Adapter {
|
|
|
91
267
|
private pump;
|
|
92
268
|
constructor(config: iMessageAdapterConfig);
|
|
93
269
|
initialize(chat: ChatInstance): Promise<void>;
|
|
270
|
+
/**
|
|
271
|
+
* Build the spectrum-ts app on demand. eve may call the adapter in an
|
|
272
|
+
* invocation that never ran `initialize()` (e.g. a Vercel Workflow reply
|
|
273
|
+
* callback), leaving `this.app` null — every send funnels through here first.
|
|
274
|
+
*/
|
|
275
|
+
private ensureApp;
|
|
276
|
+
private buildApp;
|
|
94
277
|
/**
|
|
95
278
|
* Handle a Spectrum Cloud webhook delivery (signed JSON `messages` event).
|
|
96
279
|
*
|
|
@@ -102,16 +285,105 @@ declare class iMessageAdapter implements Adapter {
|
|
|
102
285
|
* @see https://photon.codes/docs/webhooks/overview
|
|
103
286
|
*/
|
|
104
287
|
handleWebhook(request: Request, options?: WebhookOptions): Promise<Response>;
|
|
288
|
+
/**
|
|
289
|
+
* Build the spectrum content for an outbound message. Markdown-typed inputs
|
|
290
|
+
* are sent via `markdown()` so remote iMessage renders them as native styled
|
|
291
|
+
* text; raw/string/card inputs stay plain `text()`. Returns the rendered
|
|
292
|
+
* `body` too so callers can skip an empty send.
|
|
293
|
+
*/
|
|
294
|
+
private toSpectrumContent;
|
|
105
295
|
postMessage(threadId: string, message: AdapterPostableMessage): Promise<RawMessage>;
|
|
296
|
+
/**
|
|
297
|
+
* Send a message with an iMessage expressive-send effect — a bubble effect
|
|
298
|
+
* (`slam`, `loud`, `gentle`, `invisible`) or a full-screen effect
|
|
299
|
+
* (`confetti`, `fireworks`, `balloons`, `heart`, `lasers`, `celebration`,
|
|
300
|
+
* `sparkles`, `spotlight`, `echo`). Not part of the Chat SDK `Adapter`
|
|
301
|
+
* interface — exposed as an adapter-specific extra (e.g. celebratory confetti
|
|
302
|
+
* on task completion). Remote only.
|
|
303
|
+
*
|
|
304
|
+
* The `effect` argument accepts a friendly name (`"confetti"`) or a value from
|
|
305
|
+
* the re-exported `iMessageEffect` map. Effects attach to text content only,
|
|
306
|
+
* so this requires non-empty text.
|
|
307
|
+
*/
|
|
308
|
+
sendEffect(threadId: string, message: AdapterPostableMessage, effect: IMessageMessageEffect | iMessageEffectName): Promise<RawMessage>;
|
|
309
|
+
/**
|
|
310
|
+
* Send an iMessage mini-app card — an `MSMessageExtension` balloon, the
|
|
311
|
+
* closest iMessage gets to a rich card (à la Slack Block Kit) instead of a
|
|
312
|
+
* bare link. Not part of the Chat SDK `Adapter` interface — exposed as an
|
|
313
|
+
* adapter-specific extra. Remote only.
|
|
314
|
+
*
|
|
315
|
+
* Two forms:
|
|
316
|
+
*
|
|
317
|
+
* - **Just a URL** — pass a string (or a `Promise`/thunk resolving to one, so
|
|
318
|
+
* the link can be minted at send time). This is the lightweight `app(url)`
|
|
319
|
+
* card: the URL is rendered as a mini-app with no extension identifiers
|
|
320
|
+
* required.
|
|
321
|
+
* - **A full {@link MiniAppCard}** — pass an object to control the bubble's
|
|
322
|
+
* image, captions, and the exact iMessage extension that opens on tap. Its
|
|
323
|
+
* `appName`, `teamId`, and `extensionBundleId` identify that extension.
|
|
324
|
+
*/
|
|
325
|
+
sendMiniApp(threadId: string, url: AppUrl): Promise<RawMessage>;
|
|
326
|
+
sendMiniApp(threadId: string, card: MiniAppCard): Promise<RawMessage>;
|
|
327
|
+
/**
|
|
328
|
+
* Send a native iMessage voice note — a real, playable waveform bubble (the
|
|
329
|
+
* message renders with `isAudioMessage`), not an audio file dropped in as an
|
|
330
|
+
* attachment. A natural fit for TTS-capable bots that reply with speech. Not
|
|
331
|
+
* part of the Chat SDK `Adapter` interface — exposed as an adapter-specific
|
|
332
|
+
* extra. Remote only.
|
|
333
|
+
*
|
|
334
|
+
* The `input` is either in-memory audio bytes (`Uint8Array` / `Buffer` /
|
|
335
|
+
* `ArrayBuffer`, a `Blob`, or a Chat SDK `FileUpload`) or an `http(s)` URL (a
|
|
336
|
+
* `URL` or a string) that is fetched at send time. Audio bytes need an
|
|
337
|
+
* `audio/*` MIME type — supply `options.mimeType` (e.g. `"audio/mp4"`) or an
|
|
338
|
+
* `options.name` with an audio extension when it can't be inferred.
|
|
339
|
+
*/
|
|
340
|
+
sendVoice(threadId: string, input: VoiceInput, options?: VoiceOptions): Promise<RawMessage>;
|
|
341
|
+
/**
|
|
342
|
+
* Set or clear the chat background — the wallpaper behind a conversation, an
|
|
343
|
+
* iMessage-only touch with no analog on the plain-text competitors. Not part
|
|
344
|
+
* of the Chat SDK `Adapter` interface — exposed as an adapter-specific extra.
|
|
345
|
+
* Remote only.
|
|
346
|
+
*
|
|
347
|
+
* The `input` is either the literal `"clear"` (to remove the current
|
|
348
|
+
* background), in-memory image bytes (`Uint8Array` / `Buffer` / `ArrayBuffer`,
|
|
349
|
+
* a `Blob`, or a Chat SDK `FileUpload`), or an `http(s)` URL (a `URL` or a
|
|
350
|
+
* string) that is fetched at send time. Image bytes need an `image/*` MIME
|
|
351
|
+
* type — supply `options.mimeType` (e.g. `"image/jpeg"`) or an `options.name`
|
|
352
|
+
* with an image extension when it can't be inferred.
|
|
353
|
+
*
|
|
354
|
+
* Fire-and-forget: iMessage acknowledges the control signal without returning
|
|
355
|
+
* a message, so this resolves to `void` rather than a {@link RawMessage}.
|
|
356
|
+
*/
|
|
357
|
+
setBackground(threadId: string, input: BackgroundInput, options?: BackgroundOptions): Promise<void>;
|
|
106
358
|
editMessage(threadId: string, messageId: string, message: AdapterPostableMessage): Promise<RawMessage>;
|
|
107
|
-
deleteMessage(
|
|
359
|
+
deleteMessage(threadId: string, messageId: string): Promise<void>;
|
|
108
360
|
parseMessage(raw: unknown): Message;
|
|
361
|
+
/**
|
|
362
|
+
* Fetch a single message by id. spectrum-ts can resolve a message by id
|
|
363
|
+
* (from the inbound cache or the provider's by-id lookup) even though it has
|
|
364
|
+
* no paginated history API, so single-message reads work where
|
|
365
|
+
* `fetchMessages` cannot. Returns `null` when the message can't be resolved.
|
|
366
|
+
*/
|
|
367
|
+
fetchMessage(threadId: string, messageId: string): Promise<Message | null>;
|
|
109
368
|
fetchMessages(_threadId: string, _options?: FetchOptions): Promise<FetchResult>;
|
|
110
369
|
fetchThread(_threadId: string): Promise<ThreadInfo>;
|
|
111
370
|
channelIdFromThreadId(threadId: string): string;
|
|
112
371
|
addReaction(threadId: string, messageId: string, emoji: EmojiValue | string): Promise<void>;
|
|
113
|
-
removeReaction(_threadId: string,
|
|
372
|
+
removeReaction(_threadId: string, messageId: string, emoji: EmojiValue | string): Promise<void>;
|
|
114
373
|
startTyping(threadId: string, _status?: string): Promise<void>;
|
|
374
|
+
/**
|
|
375
|
+
* Cold-start a DM with a phone number / handle. spectrum-ts resolves (or
|
|
376
|
+
* creates) the 1:1 conversation from the participant via `space.create`, so
|
|
377
|
+
* the bot can message a user it has never received from. Returns the encoded
|
|
378
|
+
* thread id, ready to pass to `postMessage`.
|
|
379
|
+
*/
|
|
380
|
+
openDM(userId: string): Promise<string>;
|
|
381
|
+
/**
|
|
382
|
+
* Mark a received message (and the conversation up to it) as read, surfacing
|
|
383
|
+
* a read receipt where iMessage supports one. Remote only; not part of the
|
|
384
|
+
* Chat SDK `Adapter` interface — exposed as an adapter-specific extra.
|
|
385
|
+
*/
|
|
386
|
+
markRead(threadId: string, messageId: string): Promise<void>;
|
|
115
387
|
openModal(triggerId: string, modal: ModalElement, contextId?: string): Promise<{
|
|
116
388
|
viewId: string;
|
|
117
389
|
}>;
|
|
@@ -124,18 +396,18 @@ declare class iMessageAdapter implements Adapter {
|
|
|
124
396
|
private routeInbound;
|
|
125
397
|
private handlePollOption;
|
|
126
398
|
/**
|
|
127
|
-
* Resolve a sendable spectrum-ts `Space` for a thread.
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
* a cold send — it rebuilds the Space from the chat GUID via
|
|
132
|
-
* `imessage(app).space.get(chatGuid)`, which works for DMs and groups alike.
|
|
133
|
-
* The rebuild can still fail when several iMessage lines are configured and
|
|
134
|
-
* spectrum-ts cannot infer which line the chat belongs to (`space.get`
|
|
135
|
-
* requires `params.phone` there). Returns `undefined` when no Space can be
|
|
136
|
-
* obtained.
|
|
399
|
+
* Resolve a sendable spectrum-ts `Space` for a thread. Prefers a cached live
|
|
400
|
+
* Space; otherwise rebuilds it from the chat GUID, passing the sending line
|
|
401
|
+
* so `space.get` can pick it when multiple lines are configured. Returns
|
|
402
|
+
* `undefined` when no Space can be obtained.
|
|
137
403
|
*/
|
|
138
404
|
private resolveSpace;
|
|
405
|
+
/**
|
|
406
|
+
* The iMessage provider's Space namespace (`get` / `create`). `HasProvider`
|
|
407
|
+
* over the default provider tuple won't narrow to `true`, so `imessage(app)`
|
|
408
|
+
* types as `never` — cast to the slice of the instance we use.
|
|
409
|
+
*/
|
|
410
|
+
private platformSpaces;
|
|
139
411
|
private requireSpace;
|
|
140
412
|
private resolveMessage;
|
|
141
413
|
}
|
|
@@ -154,11 +426,24 @@ declare function createiMessageAdapter(config?: CreateiMessageAdapterOptions): i
|
|
|
154
426
|
/**
|
|
155
427
|
* iMessage format conversion using AST-based parsing.
|
|
156
428
|
*
|
|
157
|
-
* iMessage
|
|
158
|
-
*
|
|
159
|
-
*
|
|
429
|
+
* Remote iMessage (via spectrum-ts) renders CommonMark natively as styled text
|
|
430
|
+
* (bold/italic/links etc. via UTF-16 formatting ranges), so markdown-typed
|
|
431
|
+
* outbound content is sent through spectrum's `markdown()` builder verbatim --
|
|
432
|
+
* see `renderPostableContent`. The plain-text rendering here (`fromAst`) is
|
|
433
|
+
* still used for inbound parsing, `renderFormatted`, and the raw/card fallback
|
|
434
|
+
* paths: it strips formatting markers and preserves structure (lists,
|
|
435
|
+
* blockquotes, code blocks) with whitespace.
|
|
160
436
|
*/
|
|
161
437
|
|
|
438
|
+
/**
|
|
439
|
+
* The spectrum content a postable message should be sent as: the body string
|
|
440
|
+
* plus whether spectrum should render it as native markdown (styled text) or
|
|
441
|
+
* pass it through as plain text.
|
|
442
|
+
*/
|
|
443
|
+
interface PostableContent {
|
|
444
|
+
body: string;
|
|
445
|
+
markdown: boolean;
|
|
446
|
+
}
|
|
162
447
|
declare class iMessageFormatConverter extends BaseFormatConverter {
|
|
163
448
|
/**
|
|
164
449
|
* Render an AST to iMessage plain text format.
|
|
@@ -170,7 +455,21 @@ declare class iMessageFormatConverter extends BaseFormatConverter {
|
|
|
170
455
|
* iMessage sends plain text, so we just parse it as markdown.
|
|
171
456
|
*/
|
|
172
457
|
toAst(text: string): Root;
|
|
458
|
+
/**
|
|
459
|
+
* Decide how a postable message should reach spectrum-ts.
|
|
460
|
+
*
|
|
461
|
+
* Markdown-typed inputs -- `{ markdown }` and `{ ast }` -- carry CommonMark
|
|
462
|
+
* the caller wants styled, so their source is preserved verbatim and flagged
|
|
463
|
+
* `markdown: true`; the adapter sends it via spectrum's `markdown()` builder,
|
|
464
|
+
* which renders bold/italic/links/lists as native iMessage styled text.
|
|
465
|
+
*
|
|
466
|
+
* Everything else is pass-through-as-is by contract -- a plain `string` or
|
|
467
|
+
* `{ raw }` must not have stray `*`/`_` reinterpreted as formatting, and
|
|
468
|
+
* cards fall back to plain text -- so those are rendered to plain text and
|
|
469
|
+
* flagged `markdown: false`.
|
|
470
|
+
*/
|
|
471
|
+
renderPostableContent(message: AdapterPostableMessage): PostableContent;
|
|
173
472
|
private nodeToPlainText;
|
|
174
473
|
}
|
|
175
474
|
|
|
176
|
-
export { type CreateiMessageAdapterOptions, type IMessageClientEntry, createiMessageAdapter, deriveAddress, iMessageAdapter, type iMessageAdapterConfig, type iMessageAdapterLocalConfig, type iMessageAdapterRemoteConfig, iMessageFormatConverter, type iMessageThreadId };
|
|
475
|
+
export { type BackgroundBytes, type BackgroundInput, type BackgroundOptions, type CreateiMessageAdapterOptions, type IMessageClientEntry, type MiniAppCard, type MiniAppCardLayout, type MiniAppImage, type VoiceBytes, type VoiceInput, type VoiceOptions, createiMessageAdapter, deriveAddress, iMessageAdapter, type iMessageAdapterConfig, type iMessageAdapterLocalConfig, type iMessageAdapterRemoteConfig, iMessageEffect, type iMessageEffectName, iMessageFormatConverter, type iMessageThreadId, isAppUrl, resolveBackground, resolveEffect, resolveMiniApp, resolveVoice };
|