@photon-ai/chat-adapter-imessage 1.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/LICENSE +21 -0
- package/README.md +358 -0
- package/dist/index.d.ts +175 -0
- package/dist/index.js +1021 -0
- package/dist/index.js.map +1 -0
- package/package.json +70 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Photon
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
# @photon-ai/chat-adapter-imessage
|
|
2
|
+
|
|
3
|
+
iMessage community adapter for [Chat SDK](https://chat-sdk.dev/docs). Built on [spectrum-ts](https://github.com/photon-hq/spectrum-ts), Photon's unified messaging SDK. Supports **cloud** ([Spectrum Cloud](https://app.photon.codes)), **self-hosted**, and **local** (on-device, macOS) iMessage.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add chat @photon-ai/chat-adapter-imessage
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
The adapter has three modes:
|
|
14
|
+
|
|
15
|
+
- **Cloud** (recommended) — connects to [Spectrum Cloud](https://app.photon.codes) with a project ID + secret. Runs anywhere.
|
|
16
|
+
- **Self-hosted** — connects to your own `@photon-ai/advanced-imessage` gRPC endpoint.
|
|
17
|
+
- **Local** — runs directly on a Mac, reading the local iMessage database. macOS only.
|
|
18
|
+
|
|
19
|
+
The mode is auto-detected from environment variables (see [Configuration](#configuration)).
|
|
20
|
+
|
|
21
|
+
### Cloud mode
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { Chat } from "chat";
|
|
25
|
+
import { createiMessageAdapter } from "@photon-ai/chat-adapter-imessage";
|
|
26
|
+
|
|
27
|
+
const bot = new Chat({
|
|
28
|
+
userName: "mybot",
|
|
29
|
+
adapters: {
|
|
30
|
+
imessage: createiMessageAdapter({
|
|
31
|
+
local: false,
|
|
32
|
+
projectId: process.env.IMESSAGE_PROJECT_ID,
|
|
33
|
+
projectSecret: process.env.IMESSAGE_PROJECT_SECRET,
|
|
34
|
+
}),
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
bot.onNewMention(async (thread, message) => {
|
|
39
|
+
await thread.post("Hello from iMessage!");
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Local mode
|
|
44
|
+
|
|
45
|
+
For development or self-hosted deployments on a Mac. Reads from the local iMessage database and sends via the on-device APIs. Must run on macOS with **Full Disk Access** granted.
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
import { Chat } from "chat";
|
|
49
|
+
import { createiMessageAdapter } from "@photon-ai/chat-adapter-imessage";
|
|
50
|
+
|
|
51
|
+
const bot = new Chat({
|
|
52
|
+
userName: "mybot",
|
|
53
|
+
adapters: {
|
|
54
|
+
imessage: createiMessageAdapter({ local: true }),
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
bot.onNewMention(async (thread, message) => {
|
|
59
|
+
await thread.post("Hello from iMessage!");
|
|
60
|
+
});
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Setup
|
|
64
|
+
|
|
65
|
+
### Cloud mode
|
|
66
|
+
|
|
67
|
+
1. Sign up at [app.photon.codes](https://app.photon.codes) to get your **project ID** and **project secret**.
|
|
68
|
+
2. Set `IMESSAGE_PROJECT_ID` and `IMESSAGE_PROJECT_SECRET`.
|
|
69
|
+
3. Set `IMESSAGE_LOCAL=false`.
|
|
70
|
+
|
|
71
|
+
### Self-hosted mode
|
|
72
|
+
|
|
73
|
+
Point the adapter at your own `@photon-ai/advanced-imessage` gRPC server.
|
|
74
|
+
|
|
75
|
+
1. Set `IMESSAGE_SERVER_URL` to the server's gRPC address as `host:port` (e.g. `imessage.example.com:443`).
|
|
76
|
+
2. Set `IMESSAGE_API_KEY` to the server's auth token.
|
|
77
|
+
3. Set `IMESSAGE_LOCAL=false`.
|
|
78
|
+
|
|
79
|
+
> ⚠️ **Transport change.** Previous versions of this adapter spoke HTTP/Socket.IO. It now uses gRPC via spectrum-ts, so `IMESSAGE_SERVER_URL` must be a gRPC `host:port`, **not** an `https://` URL. A bare host gets `:443` appended; any URL scheme is stripped.
|
|
80
|
+
|
|
81
|
+
For advanced multi-number setups, pass explicit `clients` instead:
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
createiMessageAdapter({
|
|
85
|
+
local: false,
|
|
86
|
+
clients: [
|
|
87
|
+
{ address: "imessage.example.com:443", token: "…", phone: "+1234567890" },
|
|
88
|
+
],
|
|
89
|
+
});
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Local mode
|
|
93
|
+
|
|
94
|
+
Local mode requires running directly on a macOS machine with iMessage. It uses Apple's native APIs — reading from the local `chat.db` and sending on-device — with no external server.
|
|
95
|
+
|
|
96
|
+
1. Grant **Full Disk Access** to your terminal or application in **System Settings → Privacy & Security → Full Disk Access**.
|
|
97
|
+
2. Ensure iMessage is signed in and working on the Mac.
|
|
98
|
+
3. No additional environment variables are required — local mode is the default.
|
|
99
|
+
|
|
100
|
+
## Receiving messages
|
|
101
|
+
|
|
102
|
+
There are two ways to receive inbound messages:
|
|
103
|
+
|
|
104
|
+
- **Webhooks** (recommended for serverless) — Spectrum Cloud delivers each message to an HTTPS endpoint as signed JSON. No long-lived connection or cron job. Remote (cloud) mode only.
|
|
105
|
+
- **Gateway listener** — `startGatewayListener()` consumes spectrum-ts's message stream in real time. Works in all modes; in serverless it needs a cron job to stay connected.
|
|
106
|
+
|
|
107
|
+
## Webhooks
|
|
108
|
+
|
|
109
|
+
In remote (cloud) mode, [Spectrum Cloud](https://app.photon.codes) can deliver inbound messages to your HTTPS endpoint as signed JSON — see the [webhook docs](https://photon.codes/docs/webhooks/overview). This is the simplest path for serverless: no cron, no persistent connection.
|
|
110
|
+
|
|
111
|
+
### 1. Register the endpoint
|
|
112
|
+
|
|
113
|
+
In the [Spectrum Cloud dashboard](https://app.photon.codes), register your endpoint URL (public HTTPS only) and copy the per-webhook **signing secret** — it is shown only once.
|
|
114
|
+
|
|
115
|
+
### 2. Configure the secret
|
|
116
|
+
|
|
117
|
+
Set `IMESSAGE_WEBHOOK_SECRET` to that signing secret. The adapter verifies the `X-Spectrum-Signature` HMAC on every delivery and rejects unsigned, mismatched, or stale (>5 min) requests.
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
IMESSAGE_WEBHOOK_SECRET=whsec_...
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### 3. Create the webhook route
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
// app/api/imessage/webhook/route.ts
|
|
127
|
+
import { after } from "next/server";
|
|
128
|
+
import { bot } from "@/lib/bot";
|
|
129
|
+
|
|
130
|
+
export async function POST(request: Request): Promise<Response> {
|
|
131
|
+
return bot.webhooks.imessage(request, {
|
|
132
|
+
waitUntil: (task) => after(() => task),
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
`bot.webhooks.imessage` calls the adapter's `handleWebhook`: it verifies the signature, parses the `messages` event, and routes the message into your bot. Processing runs in the background via `waitUntil`, so the endpoint acknowledges immediately. Spectrum Cloud retries failed deliveries with backoff and delivers at-least-once — dedupe on `X-Spectrum-Webhook-Id` + `message.id` if you need exactly-once side effects.
|
|
138
|
+
|
|
139
|
+
### Replying
|
|
140
|
+
|
|
141
|
+
A webhook delivery carries no live connection, but your bot can still respond: for a **DM**, the adapter rebuilds the thread from its address and sends, reacts, edits, and shows typing over spectrum-ts (gRPC) — no gateway needed.
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
bot.onNewMention(async (thread, message) => {
|
|
145
|
+
await thread.post("Got it!"); // works directly from a webhook delivery (DM)
|
|
146
|
+
});
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Replying into a **group** still requires that group to have been received over the [gateway listener](#gateway-setup-for-serverless) in the same session — an unseen group can't be reconstructed from its id (see [Limitations](#limitations)).
|
|
150
|
+
|
|
151
|
+
## Gateway setup for serverless
|
|
152
|
+
|
|
153
|
+
### 1. Create Gateway route
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
// app/api/imessage/gateway/route.ts
|
|
157
|
+
import { after } from "next/server";
|
|
158
|
+
import { bot } from "@/lib/bot";
|
|
159
|
+
|
|
160
|
+
export const maxDuration = 800;
|
|
161
|
+
|
|
162
|
+
export async function GET(request: Request): Promise<Response> {
|
|
163
|
+
const cronSecret = process.env.CRON_SECRET;
|
|
164
|
+
if (!cronSecret) {
|
|
165
|
+
return new Response("CRON_SECRET not configured", { status: 500 });
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const authHeader = request.headers.get("authorization");
|
|
169
|
+
if (authHeader !== `Bearer ${cronSecret}`) {
|
|
170
|
+
return new Response("Unauthorized", { status: 401 });
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const durationMs = 600 * 1000;
|
|
174
|
+
|
|
175
|
+
return bot.adapters.imessage.startGatewayListener(
|
|
176
|
+
{ waitUntil: (task) => after(() => task) },
|
|
177
|
+
durationMs
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### 2. Configure Vercel Cron
|
|
183
|
+
|
|
184
|
+
```json
|
|
185
|
+
// vercel.json
|
|
186
|
+
{
|
|
187
|
+
"crons": [
|
|
188
|
+
{
|
|
189
|
+
"path": "/api/imessage/gateway",
|
|
190
|
+
"schedule": "*/9 * * * *"
|
|
191
|
+
}
|
|
192
|
+
]
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
This runs every 9 minutes, ensuring overlap with the 10-minute listener duration.
|
|
197
|
+
|
|
198
|
+
### 3. Environment variables
|
|
199
|
+
|
|
200
|
+
`CRON_SECRET` is automatically added by Vercel when you configure cron jobs.
|
|
201
|
+
|
|
202
|
+
## Configuration
|
|
203
|
+
|
|
204
|
+
| Option | Required | Description |
|
|
205
|
+
|--------|----------|-------------|
|
|
206
|
+
| `local` | No | `true` for local, `false` for cloud/self-host. Defaults to local unless `local: false`, `IMESSAGE_LOCAL=false`, or remote credentials (cloud/self-host) are provided |
|
|
207
|
+
| `projectId` | Cloud | Spectrum Cloud project ID. Auto-detected from `IMESSAGE_PROJECT_ID` |
|
|
208
|
+
| `projectSecret` | Cloud | Spectrum Cloud project secret. Auto-detected from `IMESSAGE_PROJECT_SECRET` |
|
|
209
|
+
| `serverUrl` | Self-host | gRPC `host:port` of your iMessage server. Auto-detected from `IMESSAGE_SERVER_URL` |
|
|
210
|
+
| `apiKey` | Self-host | Auth token for the self-hosted server. Auto-detected from `IMESSAGE_API_KEY` |
|
|
211
|
+
| `clients` | No | Explicit `{ address, token, phone }[]` for multi-number self-host setups |
|
|
212
|
+
| `phone` | No | Routing/identity phone for legacy self-host (defaults to `"shared"`). Auto-detected from `IMESSAGE_PHONE` |
|
|
213
|
+
| `webhookSecret` | No | Per-webhook signing secret for verifying Spectrum Cloud deliveries. Required to receive [webhooks](#webhooks). Auto-detected from `IMESSAGE_WEBHOOK_SECRET` |
|
|
214
|
+
| `logger` | No | Logger instance (defaults to `ConsoleLogger("info")`) |
|
|
215
|
+
|
|
216
|
+
## Environment variables
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
# .env.local
|
|
220
|
+
IMESSAGE_LOCAL=false # "false" for cloud/self-host (default: true)
|
|
221
|
+
|
|
222
|
+
# Cloud (recommended)
|
|
223
|
+
IMESSAGE_PROJECT_ID=...
|
|
224
|
+
IMESSAGE_PROJECT_SECRET=...
|
|
225
|
+
|
|
226
|
+
# Self-hosted (alternative)
|
|
227
|
+
IMESSAGE_SERVER_URL=imessage.example.com:443 # gRPC host:port (NOT an https URL)
|
|
228
|
+
IMESSAGE_API_KEY=...
|
|
229
|
+
IMESSAGE_PHONE=+1234567890 # optional, for multi-number routing
|
|
230
|
+
|
|
231
|
+
# Webhooks (remote/cloud only; see "Webhooks")
|
|
232
|
+
IMESSAGE_WEBHOOK_SECRET=whsec_... # per-webhook signing secret
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## Features
|
|
236
|
+
|
|
237
|
+
| Feature | Supported |
|
|
238
|
+
|---------|-----------|
|
|
239
|
+
| Mentions | DMs only |
|
|
240
|
+
| DMs | Yes |
|
|
241
|
+
| File uploads | Yes (send) |
|
|
242
|
+
| Reactions (add) | Remote only |
|
|
243
|
+
| Reactions (remove) | No |
|
|
244
|
+
| Message editing | Remote only |
|
|
245
|
+
| Typing indicator | Remote only |
|
|
246
|
+
| Modals | Limited (Remote only) |
|
|
247
|
+
| Message history | No |
|
|
248
|
+
| Thread/chat info | No |
|
|
249
|
+
| Cards | No |
|
|
250
|
+
| Streaming | No |
|
|
251
|
+
| Ephemeral messages | No |
|
|
252
|
+
| Webhooks | Yes (remote — Spectrum Cloud delivery) |
|
|
253
|
+
|
|
254
|
+
> **Remote** means cloud or self-hosted mode (anything other than `local: true`).
|
|
255
|
+
|
|
256
|
+
## Modals (Limited)
|
|
257
|
+
|
|
258
|
+
Remote mode supports limited modal functionality by mapping the Chat SDK's `openModal()` to iMessage native polls. Only `Select` children are supported — the first `Select` in the modal becomes a poll.
|
|
259
|
+
|
|
260
|
+
- `Modal.title` becomes the poll question.
|
|
261
|
+
- `Select.options` become the poll choices (2–10 supported).
|
|
262
|
+
- Votes trigger `onModalSubmit` with the selected option's `value`.
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
import { Chat, Modal, Select, SelectOption } from "chat";
|
|
266
|
+
import { createiMessageAdapter } from "@photon-ai/chat-adapter-imessage";
|
|
267
|
+
|
|
268
|
+
const bot = new Chat({
|
|
269
|
+
userName: "mybot",
|
|
270
|
+
adapters: {
|
|
271
|
+
imessage: createiMessageAdapter({ local: false }),
|
|
272
|
+
},
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
bot.onNewMention(async (thread, message) => {
|
|
276
|
+
await message.openModal(
|
|
277
|
+
Modal({
|
|
278
|
+
callbackId: "fav-color",
|
|
279
|
+
title: "What is your favorite color?",
|
|
280
|
+
children: [
|
|
281
|
+
Select({
|
|
282
|
+
id: "color",
|
|
283
|
+
label: "Pick a color",
|
|
284
|
+
options: [
|
|
285
|
+
SelectOption({ label: "Red", value: "red" }),
|
|
286
|
+
SelectOption({ label: "Blue", value: "blue" }),
|
|
287
|
+
SelectOption({ label: "Green", value: "green" }),
|
|
288
|
+
],
|
|
289
|
+
}),
|
|
290
|
+
],
|
|
291
|
+
})
|
|
292
|
+
);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
bot.onModalSubmit("fav-color", async (event) => {
|
|
296
|
+
const color = event.values.color; // "red", "blue", or "green"
|
|
297
|
+
});
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
**Not supported:** `Select.placeholder`/`label`, `TextInput`, `RadioSelect`, `Modal.submitLabel`/`closeLabel`, more than one `Select`, and poll vote *deselection*. Polls in the same chat must have distinct titles (votes are matched back to the modal by title). Local mode throws `NotImplementedError`.
|
|
301
|
+
|
|
302
|
+
## Tapback reactions
|
|
303
|
+
|
|
304
|
+
iMessage uses tapbacks instead of emoji reactions. The adapter maps standard emoji names to iMessage tapbacks:
|
|
305
|
+
|
|
306
|
+
| Emoji name | Tapback |
|
|
307
|
+
|------------|---------|
|
|
308
|
+
| `love` / `heart` | Love |
|
|
309
|
+
| `like` / `thumbs_up` | Like |
|
|
310
|
+
| `dislike` / `thumbs_down` | Dislike |
|
|
311
|
+
| `laugh` | Laugh |
|
|
312
|
+
| `emphasize` / `exclamation` | Emphasize |
|
|
313
|
+
| `question` | Question |
|
|
314
|
+
|
|
315
|
+
## Limitations
|
|
316
|
+
|
|
317
|
+
- **DMs send cold; groups are session-bound.** For a **DM**, the adapter rebuilds the thread from its address via spectrum-ts (gRPC), so it can send, react, edit, and show typing even into a thread it hasn't seen this session — including a [webhook](#webhooks) delivery. A **group** chat has no by-id resolver, so addressing one requires it to have been received over the gateway/stream in the current session; cold sends to an unseen group throw `NotImplementedError`. (Local mode cannot create spaces at all — it only replies to received messages.)
|
|
318
|
+
- **No message history.** `fetchMessages` is not supported — spectrum-ts exposes no paginated history API.
|
|
319
|
+
- **No thread/chat info.** `fetchThread` is not supported.
|
|
320
|
+
- **No reaction removal.** `removeReaction` is not supported.
|
|
321
|
+
- **Local mode** supports sending and receiving (reply to an inbound message), but not reactions, typing, editing, modals, history, or thread info.
|
|
322
|
+
- **Formatting.** iMessage is plain-text only; Markdown formatting is stripped when sending, preserving the text content.
|
|
323
|
+
- **Platform.** Local mode requires macOS. Cloud and self-host run anywhere.
|
|
324
|
+
- **Cards.** iMessage has no structured card layouts.
|
|
325
|
+
|
|
326
|
+
## Breaking changes
|
|
327
|
+
|
|
328
|
+
This version re-platforms the adapter onto **spectrum-ts**. If you are upgrading:
|
|
329
|
+
|
|
330
|
+
- **Dependency** — replaces `@photon-ai/imessage-kit` + `@photon-ai/advanced-imessage-kit` with `spectrum-ts`.
|
|
331
|
+
- **`IMESSAGE_SERVER_URL` is now a gRPC `host:port`** (self-host), not an `https://` / Socket.IO URL.
|
|
332
|
+
- **New cloud path** — set `IMESSAGE_PROJECT_ID` + `IMESSAGE_PROJECT_SECRET` for Spectrum Cloud.
|
|
333
|
+
- **Removed capabilities** (now `NotImplementedError`): `fetchMessages`, `fetchThread`, `removeReaction`, and cold `postMessage` to an unseen **group** thread (DMs are rebuilt from their address over gRPC — see [Limitations](#limitations)). Local `fetchMessages` (previously supported) is also removed.
|
|
334
|
+
- **`adapter.sdk` → `adapter.app`** — the adapter now exposes the underlying `SpectrumInstance` as `adapter.app` (null until `initialize()`).
|
|
335
|
+
|
|
336
|
+
## Troubleshooting
|
|
337
|
+
|
|
338
|
+
### "serverUrl is required when local is false"
|
|
339
|
+
|
|
340
|
+
- Provide cloud credentials (`IMESSAGE_PROJECT_ID` + `IMESSAGE_PROJECT_SECRET`), or a self-host `IMESSAGE_SERVER_URL` + `IMESSAGE_API_KEY`.
|
|
341
|
+
|
|
342
|
+
### Self-host connection issues
|
|
343
|
+
|
|
344
|
+
- Confirm `IMESSAGE_SERVER_URL` is a gRPC `host:port` (e.g. `imessage.example.com:443`), not an `https://` URL.
|
|
345
|
+
- Verify the token matches your server's credentials.
|
|
346
|
+
|
|
347
|
+
### Local mode not receiving messages
|
|
348
|
+
|
|
349
|
+
- Verify **Full Disk Access** is granted to your terminal or application.
|
|
350
|
+
- Check that iMessage is signed in and working.
|
|
351
|
+
|
|
352
|
+
### `NotImplementedError` from `fetchMessages` / `fetchThread` / `removeReaction`
|
|
353
|
+
|
|
354
|
+
- These are not supported by spectrum-ts. See [Limitations](#limitations).
|
|
355
|
+
|
|
356
|
+
## License
|
|
357
|
+
|
|
358
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
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';
|
|
3
|
+
|
|
4
|
+
/** Thread ID components for iMessage */
|
|
5
|
+
interface iMessageThreadId {
|
|
6
|
+
/** Chat GUID (e.g., "iMessage;-;+1234567890") */
|
|
7
|
+
chatGuid: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Explicit self-hosted iMessage client entry, passed straight through to
|
|
11
|
+
* spectrum-ts's `imessage.config({ clients: [...] })`.
|
|
12
|
+
*
|
|
13
|
+
* - `address`: gRPC endpoint (`host:port`) of an `@photon-ai/advanced-imessage`
|
|
14
|
+
* server.
|
|
15
|
+
* - `token`: auth token for that server.
|
|
16
|
+
* - `phone`: the number this client sends/receives as (routing key in
|
|
17
|
+
* multi-number setups; use the `"shared"` sentinel for single-number).
|
|
18
|
+
*/
|
|
19
|
+
interface IMessageClientEntry {
|
|
20
|
+
address: string;
|
|
21
|
+
phone: string;
|
|
22
|
+
token: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface iMessageAdapterLocalConfig {
|
|
26
|
+
/** Unused in local mode; accepted for symmetry/back-compat. */
|
|
27
|
+
apiKey?: string;
|
|
28
|
+
local: true;
|
|
29
|
+
logger: Logger;
|
|
30
|
+
/** Unused in local mode; accepted for symmetry/back-compat. */
|
|
31
|
+
serverUrl?: string;
|
|
32
|
+
}
|
|
33
|
+
interface iMessageAdapterRemoteConfig {
|
|
34
|
+
/** Legacy self-host token. Mapped to a `clients` entry's `token`. */
|
|
35
|
+
apiKey?: string;
|
|
36
|
+
/** Explicit self-host gRPC clients (advanced). */
|
|
37
|
+
clients?: IMessageClientEntry | IMessageClientEntry[];
|
|
38
|
+
local: false;
|
|
39
|
+
logger: Logger;
|
|
40
|
+
/** Routing/identity phone for legacy self-host (defaults to `"shared"`). */
|
|
41
|
+
phone?: string;
|
|
42
|
+
/** Spectrum Cloud project id (recommended remote path). */
|
|
43
|
+
projectId?: string;
|
|
44
|
+
/** Spectrum Cloud project secret (recommended remote path). */
|
|
45
|
+
projectSecret?: string;
|
|
46
|
+
/** Legacy self-host endpoint. Now a gRPC `host:port` (see README). */
|
|
47
|
+
serverUrl?: string;
|
|
48
|
+
/** Per-webhook signing secret for verifying Spectrum Cloud deliveries. */
|
|
49
|
+
webhookSecret?: string;
|
|
50
|
+
}
|
|
51
|
+
type iMessageAdapterConfig = iMessageAdapterLocalConfig | iMessageAdapterRemoteConfig;
|
|
52
|
+
interface CreateiMessageAdapterOptions {
|
|
53
|
+
apiKey?: string;
|
|
54
|
+
clients?: IMessageClientEntry | IMessageClientEntry[];
|
|
55
|
+
local?: boolean;
|
|
56
|
+
logger?: Logger;
|
|
57
|
+
phone?: string;
|
|
58
|
+
projectId?: string;
|
|
59
|
+
projectSecret?: string;
|
|
60
|
+
serverUrl?: string;
|
|
61
|
+
webhookSecret?: string;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Normalize a legacy `serverUrl` into a gRPC `host:port` address.
|
|
65
|
+
*
|
|
66
|
+
* `@photon-ai/advanced-imessage` (the transport spectrum-ts uses) speaks gRPC,
|
|
67
|
+
* not HTTP/Socket.IO, so any scheme is stripped and a default `:443` port is
|
|
68
|
+
* appended when none is present.
|
|
69
|
+
*/
|
|
70
|
+
declare function deriveAddress(serverUrl: string): string;
|
|
71
|
+
|
|
72
|
+
declare class iMessageAdapter implements Adapter {
|
|
73
|
+
readonly name = "imessage";
|
|
74
|
+
readonly userName: string;
|
|
75
|
+
readonly local: boolean;
|
|
76
|
+
readonly serverUrl?: string;
|
|
77
|
+
readonly apiKey?: string;
|
|
78
|
+
readonly projectId?: string;
|
|
79
|
+
readonly projectSecret?: string;
|
|
80
|
+
readonly clients?: IMessageClientEntry[];
|
|
81
|
+
readonly phone?: string;
|
|
82
|
+
readonly webhookSecret?: string;
|
|
83
|
+
/** The spectrum-ts instance — null until `initialize()` runs. */
|
|
84
|
+
app: SpectrumInstance | null;
|
|
85
|
+
private chat;
|
|
86
|
+
private readonly logger;
|
|
87
|
+
private readonly formatConverter;
|
|
88
|
+
private readonly cache;
|
|
89
|
+
private readonly modals;
|
|
90
|
+
private gatewayOptions?;
|
|
91
|
+
private pump;
|
|
92
|
+
constructor(config: iMessageAdapterConfig);
|
|
93
|
+
initialize(chat: ChatInstance): Promise<void>;
|
|
94
|
+
/**
|
|
95
|
+
* Handle a Spectrum Cloud webhook delivery (signed JSON `messages` event).
|
|
96
|
+
*
|
|
97
|
+
* Verifies the `X-Spectrum-Signature` HMAC, then routes the message into the
|
|
98
|
+
* Chat SDK. Webhook deliveries are receive-only: a delivered thread has no
|
|
99
|
+
* live spectrum-ts `Space`, so replying/reacting still requires the gateway
|
|
100
|
+
* stream (see `startGatewayListener`).
|
|
101
|
+
*
|
|
102
|
+
* @see https://photon.codes/docs/webhooks/overview
|
|
103
|
+
*/
|
|
104
|
+
handleWebhook(request: Request, options?: WebhookOptions): Promise<Response>;
|
|
105
|
+
postMessage(threadId: string, message: AdapterPostableMessage): Promise<RawMessage>;
|
|
106
|
+
editMessage(threadId: string, messageId: string, message: AdapterPostableMessage): Promise<RawMessage>;
|
|
107
|
+
deleteMessage(_threadId: string, _messageId: string): Promise<void>;
|
|
108
|
+
parseMessage(raw: unknown): Message;
|
|
109
|
+
fetchMessages(_threadId: string, _options?: FetchOptions): Promise<FetchResult>;
|
|
110
|
+
fetchThread(_threadId: string): Promise<ThreadInfo>;
|
|
111
|
+
channelIdFromThreadId(threadId: string): string;
|
|
112
|
+
addReaction(threadId: string, messageId: string, emoji: EmojiValue | string): Promise<void>;
|
|
113
|
+
removeReaction(_threadId: string, _messageId: string, _emoji: EmojiValue | string): Promise<void>;
|
|
114
|
+
startTyping(threadId: string, _status?: string): Promise<void>;
|
|
115
|
+
openModal(triggerId: string, modal: ModalElement, contextId?: string): Promise<{
|
|
116
|
+
viewId: string;
|
|
117
|
+
}>;
|
|
118
|
+
renderFormatted(content: FormattedContent): string;
|
|
119
|
+
encodeThreadId(platformData: iMessageThreadId): string;
|
|
120
|
+
decodeThreadId(threadId: string): iMessageThreadId;
|
|
121
|
+
isDM(threadId: string): boolean;
|
|
122
|
+
startGatewayListener(options: WebhookOptions, durationMs?: number, abortSignal?: AbortSignal): Promise<Response>;
|
|
123
|
+
private routeWebhookMessage;
|
|
124
|
+
private routeInbound;
|
|
125
|
+
private handlePollOption;
|
|
126
|
+
/**
|
|
127
|
+
* Resolve a sendable spectrum-ts `Space` for a thread.
|
|
128
|
+
*
|
|
129
|
+
* Prefers a live `Space` cached from the inbound stream (correct sending
|
|
130
|
+
* line, no extra round-trip). On a miss — e.g. a webhook-only deployment, or
|
|
131
|
+
* a cold send — it reconstructs the Space over gRPC via
|
|
132
|
+
* `imessage(app).space([address])`. Only DMs can be rebuilt from a chatGuid:
|
|
133
|
+
* the resolver derives them from the peer address, whereas an unseen group
|
|
134
|
+
* has no by-id resolver (passing participants would create a *new* chat).
|
|
135
|
+
* Returns `undefined` when no Space can be obtained.
|
|
136
|
+
*/
|
|
137
|
+
private resolveSpace;
|
|
138
|
+
private requireSpace;
|
|
139
|
+
private resolveMessage;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Construct an {@link iMessageAdapter}, filling unset options from environment
|
|
144
|
+
* variables.
|
|
145
|
+
*
|
|
146
|
+
* Mode is chosen by an explicit local signal first — `config.local`, then
|
|
147
|
+
* `IMESSAGE_LOCAL` — otherwise remote when remote credentials are present (cloud
|
|
148
|
+
* `projectId` + `projectSecret`, or self-host `clients` / `serverUrl` +
|
|
149
|
+
* `apiKey`), else local.
|
|
150
|
+
*/
|
|
151
|
+
declare function createiMessageAdapter(config?: CreateiMessageAdapterOptions): iMessageAdapter;
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* iMessage format conversion using AST-based parsing.
|
|
155
|
+
*
|
|
156
|
+
* iMessage supports plain text only -- no rich formatting syntax.
|
|
157
|
+
* The converter strips formatting markers and outputs clean plain text,
|
|
158
|
+
* preserving structure (lists, blockquotes, code blocks) with whitespace.
|
|
159
|
+
*/
|
|
160
|
+
|
|
161
|
+
declare class iMessageFormatConverter extends BaseFormatConverter {
|
|
162
|
+
/**
|
|
163
|
+
* Render an AST to iMessage plain text format.
|
|
164
|
+
* Strips all formatting markers since iMessage doesn't support rich text via API.
|
|
165
|
+
*/
|
|
166
|
+
fromAst(ast: Root): string;
|
|
167
|
+
/**
|
|
168
|
+
* Parse iMessage text into an AST.
|
|
169
|
+
* iMessage sends plain text, so we just parse it as markdown.
|
|
170
|
+
*/
|
|
171
|
+
toAst(text: string): Root;
|
|
172
|
+
private nodeToPlainText;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export { type CreateiMessageAdapterOptions, type IMessageClientEntry, createiMessageAdapter, deriveAddress, iMessageAdapter, type iMessageAdapterConfig, type iMessageAdapterLocalConfig, type iMessageAdapterRemoteConfig, iMessageFormatConverter, type iMessageThreadId };
|