@photon-ai/chat-adapter-imessage 1.1.0 → 2.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/README.md +6 -6
- package/dist/index.d.ts +9 -8
- package/dist/index.js +21 -21
- package/dist/index.js.map +1 -1
- package/package.json +4 -26
package/README.md
CHANGED
|
@@ -138,15 +138,15 @@ export async function POST(request: Request): Promise<Response> {
|
|
|
138
138
|
|
|
139
139
|
### Replying
|
|
140
140
|
|
|
141
|
-
A webhook delivery carries no live connection, but your bot can still respond:
|
|
141
|
+
A webhook delivery carries no live connection, but your bot can still respond: the adapter rebuilds the thread — **DM or group** — from its chat GUID via spectrum-ts's `space.get` and sends, reacts, edits, and shows typing — no gateway needed.
|
|
142
142
|
|
|
143
143
|
```typescript
|
|
144
144
|
bot.onNewMention(async (thread, message) => {
|
|
145
|
-
await thread.post("Got it!"); // works directly from a webhook delivery
|
|
145
|
+
await thread.post("Got it!"); // works directly from a webhook delivery
|
|
146
146
|
});
|
|
147
147
|
```
|
|
148
148
|
|
|
149
|
-
|
|
149
|
+
The rebuild needs spectrum-ts to know which iMessage line the chat belongs to; with **multiple lines** configured, an unseen thread can't be resolved and the adapter throws `NotImplementedError` — respond within a thread received over the [gateway listener](#gateway-setup-for-serverless) instead (see [Limitations](#limitations)).
|
|
150
150
|
|
|
151
151
|
## Gateway setup for serverless
|
|
152
152
|
|
|
@@ -314,11 +314,11 @@ iMessage uses tapbacks instead of emoji reactions. The adapter maps standard emo
|
|
|
314
314
|
|
|
315
315
|
## Limitations
|
|
316
316
|
|
|
317
|
-
- **
|
|
317
|
+
- **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
318
|
- **No message history.** `fetchMessages` is not supported — spectrum-ts exposes no paginated history API.
|
|
319
319
|
- **No thread/chat info.** `fetchThread` is not supported.
|
|
320
320
|
- **No reaction removal.** `removeReaction` is not supported.
|
|
321
|
-
- **Local mode** supports sending
|
|
321
|
+
- **Local mode** supports sending (including cold sends by chat GUID) and receiving, but not reactions, typing, editing, modals, history, or thread info.
|
|
322
322
|
- **Formatting.** iMessage is plain-text only; Markdown formatting is stripped when sending, preserving the text content.
|
|
323
323
|
- **Platform.** Local mode requires macOS. Cloud and self-host run anywhere.
|
|
324
324
|
- **Cards.** iMessage has no structured card layouts.
|
|
@@ -330,7 +330,7 @@ This version re-platforms the adapter onto **spectrum-ts**. If you are upgrading
|
|
|
330
330
|
- **Dependency** — replaces `@photon-ai/imessage-kit` + `@photon-ai/advanced-imessage-kit` with `spectrum-ts`.
|
|
331
331
|
- **`IMESSAGE_SERVER_URL` is now a gRPC `host:port`** (self-host), not an `https://` / Socket.IO URL.
|
|
332
332
|
- **New cloud path** — set `IMESSAGE_PROJECT_ID` + `IMESSAGE_PROJECT_SECRET` for Spectrum Cloud.
|
|
333
|
-
- **Removed capabilities** (now `NotImplementedError`): `fetchMessages`, `fetchThread`, `removeReaction
|
|
333
|
+
- **Removed capabilities** (now `NotImplementedError`): `fetchMessages`, `fetchThread`, and `removeReaction`. Local `fetchMessages` (previously supported) is also removed. Cold `postMessage` works for DMs and groups alike (rebuilt from the chat GUID — see [Limitations](#limitations)).
|
|
334
334
|
- **`adapter.sdk` → `adapter.app`** — the adapter now exposes the underlying `SpectrumInstance` as `adapter.app` (null until `initialize()`).
|
|
335
335
|
|
|
336
336
|
## Troubleshooting
|
package/dist/index.d.ts
CHANGED
|
@@ -95,9 +95,9 @@ declare class iMessageAdapter implements Adapter {
|
|
|
95
95
|
* Handle a Spectrum Cloud webhook delivery (signed JSON `messages` event).
|
|
96
96
|
*
|
|
97
97
|
* Verifies the `X-Spectrum-Signature` HMAC, then routes the message into the
|
|
98
|
-
* Chat SDK.
|
|
99
|
-
*
|
|
100
|
-
*
|
|
98
|
+
* Chat SDK. A delivered thread has no live spectrum-ts `Space`, but the
|
|
99
|
+
* adapter rebuilds one from the chat GUID on demand (see `resolveSpace`), so
|
|
100
|
+
* replying works directly from a webhook delivery.
|
|
101
101
|
*
|
|
102
102
|
* @see https://photon.codes/docs/webhooks/overview
|
|
103
103
|
*/
|
|
@@ -128,11 +128,12 @@ declare class iMessageAdapter implements Adapter {
|
|
|
128
128
|
*
|
|
129
129
|
* Prefers a live `Space` cached from the inbound stream (correct sending
|
|
130
130
|
* line, no extra round-trip). On a miss — e.g. a webhook-only deployment, or
|
|
131
|
-
* a cold send — it
|
|
132
|
-
* `imessage(app).space(
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
* Returns `undefined` when no Space can be
|
|
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.
|
|
136
137
|
*/
|
|
137
138
|
private resolveSpace;
|
|
138
139
|
private requireSpace;
|
package/dist/index.js
CHANGED
|
@@ -192,11 +192,6 @@ function decodeThreadId(threadId) {
|
|
|
192
192
|
function isDMChatGuid(chatGuid) {
|
|
193
193
|
return chatGuid.includes(";-;");
|
|
194
194
|
}
|
|
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
195
|
|
|
201
196
|
// src/internal/inbound.ts
|
|
202
197
|
function buildChatMessageFromFields(fields) {
|
|
@@ -566,9 +561,9 @@ var iMessageAdapter = class {
|
|
|
566
561
|
* Handle a Spectrum Cloud webhook delivery (signed JSON `messages` event).
|
|
567
562
|
*
|
|
568
563
|
* Verifies the `X-Spectrum-Signature` HMAC, then routes the message into the
|
|
569
|
-
* Chat SDK.
|
|
570
|
-
*
|
|
571
|
-
*
|
|
564
|
+
* Chat SDK. A delivered thread has no live spectrum-ts `Space`, but the
|
|
565
|
+
* adapter rebuilds one from the chat GUID on demand (see `resolveSpace`), so
|
|
566
|
+
* replying works directly from a webhook delivery.
|
|
572
567
|
*
|
|
573
568
|
* @see https://photon.codes/docs/webhooks/overview
|
|
574
569
|
*/
|
|
@@ -896,11 +891,12 @@ var iMessageAdapter = class {
|
|
|
896
891
|
*
|
|
897
892
|
* Prefers a live `Space` cached from the inbound stream (correct sending
|
|
898
893
|
* 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
|
|
894
|
+
* a cold send — it rebuilds the Space from the chat GUID via
|
|
895
|
+
* `imessage(app).space.get(chatGuid)`, which works for DMs and groups alike.
|
|
896
|
+
* The rebuild can still fail when several iMessage lines are configured and
|
|
897
|
+
* spectrum-ts cannot infer which line the chat belongs to (`space.get`
|
|
898
|
+
* requires `params.phone` there). Returns `undefined` when no Space can be
|
|
899
|
+
* obtained.
|
|
904
900
|
*/
|
|
905
901
|
async resolveSpace(threadId) {
|
|
906
902
|
const { chatGuid } = decodeThreadId(threadId);
|
|
@@ -908,23 +904,27 @@ var iMessageAdapter = class {
|
|
|
908
904
|
if (cached) {
|
|
909
905
|
return cached;
|
|
910
906
|
}
|
|
911
|
-
if (!this.app
|
|
907
|
+
if (!this.app) {
|
|
912
908
|
return;
|
|
913
909
|
}
|
|
914
|
-
const
|
|
915
|
-
|
|
910
|
+
const platform = imessage(this.app);
|
|
911
|
+
try {
|
|
912
|
+
const space = await platform.space.get(chatGuid);
|
|
913
|
+
this.cache.rememberSpace(space);
|
|
914
|
+
return space;
|
|
915
|
+
} catch (error) {
|
|
916
|
+
this.logger.debug("Could not rebuild Space from chat GUID", {
|
|
917
|
+
chatGuid,
|
|
918
|
+
error: String(error)
|
|
919
|
+
});
|
|
916
920
|
return;
|
|
917
921
|
}
|
|
918
|
-
const platform = imessage(this.app);
|
|
919
|
-
const space = await platform.space([address]);
|
|
920
|
-
this.cache.rememberSpace(space);
|
|
921
|
-
return space;
|
|
922
922
|
}
|
|
923
923
|
async requireSpace(threadId, action) {
|
|
924
924
|
const space = await this.resolveSpace(threadId);
|
|
925
925
|
if (!space) {
|
|
926
926
|
throw new NotImplementedError(
|
|
927
|
-
`${action}
|
|
927
|
+
`${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
928
|
action
|
|
929
929
|
);
|
|
930
930
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/adapter.ts","../src/config.ts","../src/internal/cache.ts","../src/internal/gateway.ts","../src/internal/inbound.ts","../src/internal/thread.ts","../src/internal/modals.ts","../src/internal/outbound.ts","../src/internal/webhook.ts","../src/markdown.ts","../src/factory.ts"],"sourcesContent":["import { extractFiles, ValidationError } from \"@chat-adapter/shared\";\nimport type {\n Adapter,\n AdapterPostableMessage,\n ChatInstance,\n EmojiValue,\n FetchOptions,\n FetchResult,\n FormattedContent,\n Logger,\n Message,\n ModalElement,\n RawMessage,\n SelectElement,\n ThreadInfo,\n WebhookOptions,\n} from \"chat\";\nimport { NotImplementedError } from \"chat\";\nimport {\n poll as pollContent,\n Spectrum,\n type SpectrumInstance,\n type Message as SpectrumMessage,\n type Space as SpectrumSpace,\n text as textContent,\n} from \"spectrum-ts\";\nimport { imessage } from \"spectrum-ts/providers/imessage\";\nimport { type iMessageAdapterConfig, resolveSpectrumConfig } from \"./config\";\nimport { InboundCache } from \"./internal/cache\";\nimport { MessagePump } from \"./internal/gateway\";\nimport { buildChatMessage } from \"./internal/inbound\";\nimport { ModalPollRegistry } from \"./internal/modals\";\nimport { emojiToGlyph, fileToAttachment } from \"./internal/outbound\";\nimport {\n decodeThreadId,\n dmAddressFromChatGuid,\n encodeThreadId,\n isDMChatGuid,\n} from \"./internal/thread\";\nimport {\n buildChatMessageFromWebhook,\n SPECTRUM_EVENT_HEADER,\n SPECTRUM_MESSAGES_EVENT,\n SPECTRUM_SIGNATURE_HEADER,\n SPECTRUM_TIMESTAMP_HEADER,\n type SpectrumWebhookPayload,\n verifySpectrumSignature,\n} from \"./internal/webhook\";\nimport { iMessageFormatConverter } from \"./markdown\";\nimport type { IMessageClientEntry, iMessageThreadId } from \"./types\";\n\nconst TYPING_DURATION_MS = 3000;\n\nexport class iMessageAdapter implements Adapter {\n readonly name = \"imessage\";\n readonly userName: string = \"\";\n readonly local: boolean;\n readonly serverUrl?: string;\n readonly apiKey?: string;\n readonly projectId?: string;\n readonly projectSecret?: string;\n readonly clients?: IMessageClientEntry[];\n readonly phone?: string;\n readonly webhookSecret?: string;\n\n /** The spectrum-ts instance — null until `initialize()` runs. */\n app: SpectrumInstance | null = null;\n\n private chat: ChatInstance | null = null;\n private readonly logger: Logger;\n private readonly formatConverter = new iMessageFormatConverter();\n private readonly cache = new InboundCache();\n private readonly modals = new ModalPollRegistry();\n\n private gatewayOptions?: WebhookOptions;\n private pump: MessagePump | null = null;\n\n constructor(config: iMessageAdapterConfig) {\n if (config.local && process.platform !== \"darwin\") {\n throw new ValidationError(\n \"imessage\",\n \"iMessage adapter local mode requires macOS. Current platform: \" +\n process.platform,\n );\n }\n\n this.local = config.local;\n this.logger = config.logger;\n this.serverUrl = config.serverUrl;\n this.apiKey = config.apiKey;\n\n if (!config.local) {\n this.projectId = config.projectId;\n this.projectSecret = config.projectSecret;\n this.phone = config.phone;\n this.clients = toClientArray(config.clients);\n // Trim here too so direct `new iMessageAdapter(...)` matches the factory\n // (createiMessageAdapter trims it): a stray space would otherwise fail\n // signature verification only on the constructor path.\n this.webhookSecret = config.webhookSecret?.trim();\n }\n }\n\n async initialize(chat: ChatInstance): Promise<void> {\n this.chat = chat;\n\n const { providerConfig, projectId, projectSecret } = resolveSpectrumConfig(\n this.local,\n {\n apiKey: this.apiKey,\n clients: this.clients,\n phone: this.phone,\n projectId: this.projectId,\n projectSecret: this.projectSecret,\n serverUrl: this.serverUrl,\n },\n );\n const providers = [imessage.config(providerConfig)];\n\n this.app =\n projectId && projectSecret\n ? await Spectrum({ providers, projectId, projectSecret })\n : await Spectrum({ providers });\n\n this.pump = new MessagePump(\n () => {\n if (!this.app) {\n throw new Error(\"Adapter not initialized\");\n }\n return this.app.messages;\n },\n (space, message) =>\n this.routeInbound(space, message, this.gatewayOptions),\n this.logger,\n );\n\n let mode: \"local\" | \"cloud\" | \"self-host\";\n if (this.local) {\n mode = \"local\";\n } else if (projectId) {\n mode = \"cloud\";\n } else {\n mode = \"self-host\";\n }\n this.logger.info(\"iMessage adapter initialized\", {\n local: this.local,\n mode,\n });\n }\n\n /**\n * Handle a Spectrum Cloud webhook delivery (signed JSON `messages` event).\n *\n * Verifies the `X-Spectrum-Signature` HMAC, then routes the message into the\n * Chat SDK. Webhook deliveries are receive-only: a delivered thread has no\n * live spectrum-ts `Space`, so replying/reacting still requires the gateway\n * stream (see `startGatewayListener`).\n *\n * @see https://photon.codes/docs/webhooks/overview\n */\n async handleWebhook(\n request: Request,\n options?: WebhookOptions,\n ): Promise<Response> {\n if (!this.chat) {\n return new Response(\"Chat instance not initialized\", { status: 500 });\n }\n if (this.local) {\n return new Response(\n \"Webhooks require remote (cloud) mode — local mode receives via startGatewayListener()\",\n { status: 501 },\n );\n }\n if (!this.webhookSecret) {\n return new Response(\n \"Webhook signing secret not configured (set IMESSAGE_WEBHOOK_SECRET)\",\n { status: 500 },\n );\n }\n\n // Read the raw body BEFORE parsing: the signature covers the exact bytes.\n const rawBody = await request.text();\n const verdict = verifySpectrumSignature({\n secret: this.webhookSecret,\n signature: request.headers.get(SPECTRUM_SIGNATURE_HEADER),\n timestamp: request.headers.get(SPECTRUM_TIMESTAMP_HEADER),\n rawBody,\n });\n if (!verdict.ok) {\n this.logger.warn(\"Rejected iMessage webhook delivery\", {\n reason: verdict.reason,\n });\n return new Response(verdict.reason, { status: verdict.status });\n }\n\n const event = request.headers.get(SPECTRUM_EVENT_HEADER);\n if (event && event !== SPECTRUM_MESSAGES_EVENT) {\n // Acknowledge unrecognized event types so Cloud does not retry them.\n return new Response(null, { status: 204 });\n }\n\n let payload: SpectrumWebhookPayload;\n try {\n payload = JSON.parse(rawBody) as SpectrumWebhookPayload;\n } catch {\n return new Response(\"Invalid JSON body\", { status: 400 });\n }\n\n if (\n payload.event !== SPECTRUM_MESSAGES_EVENT ||\n !(payload.message && payload.space)\n ) {\n return new Response(null, { status: 204 });\n }\n\n this.routeWebhookMessage(payload, options);\n return new Response(null, { status: 200 });\n }\n\n async postMessage(\n threadId: string,\n message: AdapterPostableMessage,\n ): Promise<RawMessage> {\n const space = await this.requireSpace(threadId, \"postMessage\");\n const body = this.formatConverter.renderPostable(message);\n const files = extractFiles(message);\n\n let first: SpectrumMessage | undefined;\n if (body && body.trim().length > 0) {\n first = (await space.send(textContent(body))) ?? first;\n }\n for (const file of files) {\n const sent =\n (await space.send(await fileToAttachment(file))) ?? undefined;\n first ??= sent;\n }\n\n if (!first) {\n throw new ValidationError(\n \"imessage\",\n \"postMessage requires non-empty text or at least one attachment\",\n );\n }\n\n return { id: first.id, threadId, raw: first };\n }\n\n async editMessage(\n threadId: string,\n messageId: string,\n message: AdapterPostableMessage,\n ): Promise<RawMessage> {\n if (this.local) {\n throw new NotImplementedError(\n \"editMessage is not supported in local mode\",\n \"editMessage\",\n );\n }\n\n const target = await this.resolveMessage(threadId, messageId);\n if (!target) {\n throw new NotImplementedError(\n \"editMessage requires the original message to have been received in this session\",\n \"editMessage\",\n );\n }\n\n await target.edit(\n textContent(this.formatConverter.renderPostable(message)),\n );\n return { id: messageId, threadId, raw: target };\n }\n\n async deleteMessage(_threadId: string, _messageId: string): Promise<void> {\n throw new NotImplementedError(\n \"deleteMessage is not implemented\",\n \"deleteMessage\",\n );\n }\n\n parseMessage(raw: unknown): Message {\n const message = raw as SpectrumMessage;\n return buildChatMessage(message, message.space);\n }\n\n async fetchMessages(\n _threadId: string,\n _options?: FetchOptions,\n ): Promise<FetchResult> {\n throw new NotImplementedError(\n \"fetchMessages (message history) is not supported by spectrum-ts\",\n \"fetchMessages\",\n );\n }\n\n async fetchThread(_threadId: string): Promise<ThreadInfo> {\n throw new NotImplementedError(\n \"fetchThread (chat info) is not supported by spectrum-ts\",\n \"fetchThread\",\n );\n }\n\n channelIdFromThreadId(threadId: string): string {\n return threadId;\n }\n\n async addReaction(\n threadId: string,\n messageId: string,\n emoji: EmojiValue | string,\n ): Promise<void> {\n if (this.local) {\n throw new NotImplementedError(\n \"addReaction is not supported in local mode\",\n \"addReaction\",\n );\n }\n\n const glyph = emojiToGlyph(emoji);\n const target = await this.resolveMessage(threadId, messageId);\n if (!target) {\n throw new NotImplementedError(\n \"addReaction requires the target message to have been received in this session\",\n \"addReaction\",\n );\n }\n\n await target.react(glyph);\n }\n\n async removeReaction(\n _threadId: string,\n _messageId: string,\n _emoji: EmojiValue | string,\n ): Promise<void> {\n throw new NotImplementedError(\n \"removeReaction is not supported (spectrum-ts has no reaction-removal API)\",\n \"removeReaction\",\n );\n }\n\n async startTyping(threadId: string, _status?: string): Promise<void> {\n if (this.local) {\n throw new NotImplementedError(\n \"startTyping is not supported in local mode\",\n \"startTyping\",\n );\n }\n\n const space = await this.requireSpace(threadId, \"startTyping\");\n await space.startTyping();\n setTimeout(() => {\n space.stopTyping().catch(() => {\n // best-effort; ignore failures\n });\n }, TYPING_DURATION_MS);\n }\n\n async openModal(\n triggerId: string,\n modal: ModalElement,\n contextId?: string,\n ): Promise<{ viewId: string }> {\n if (this.local) {\n throw new NotImplementedError(\n \"openModal is not supported in local mode\",\n \"openModal\",\n );\n }\n\n const select = modal.children.find(\n (c): c is SelectElement => c.type === \"select\",\n );\n if (!select) {\n throw new ValidationError(\n \"imessage\",\n \"openModal requires at least one Select child — iMessage modals map to native polls\",\n );\n }\n\n const labels = select.options.map((o) => o.label);\n if (labels.length < 2 || labels.length > 10) {\n throw new ValidationError(\n \"imessage\",\n `iMessage polls require between 2 and 10 options, received ${labels.length}`,\n );\n }\n\n const { chatGuid } = decodeThreadId(triggerId);\n const space = await this.requireSpace(triggerId, \"openModal\");\n\n const sent = await space.send(pollContent(modal.title, labels));\n const viewId = sent?.id ?? `poll-${Date.now()}`;\n\n this.modals.register(chatGuid, modal.title, {\n viewId,\n callbackId: modal.callbackId,\n selectId: select.id,\n options: select.options,\n contextId,\n privateMetadata: modal.privateMetadata,\n });\n\n return { viewId };\n }\n\n renderFormatted(content: FormattedContent): string {\n return this.formatConverter.fromAst(content);\n }\n\n encodeThreadId(platformData: iMessageThreadId): string {\n return encodeThreadId(platformData);\n }\n\n decodeThreadId(threadId: string): iMessageThreadId {\n return decodeThreadId(threadId);\n }\n\n isDM(threadId: string): boolean {\n return isDMChatGuid(decodeThreadId(threadId).chatGuid);\n }\n\n async startGatewayListener(\n options: WebhookOptions,\n durationMs = 180_000,\n abortSignal?: AbortSignal,\n ): Promise<Response> {\n if (!this.chat) {\n return new Response(\"Chat instance not initialized\", { status: 500 });\n }\n if (!options.waitUntil) {\n return new Response(\"waitUntil not provided\", { status: 500 });\n }\n if (!(this.app && this.pump)) {\n return new Response(\"Adapter not initialized\", { status: 500 });\n }\n\n this.logger.info(\"Starting iMessage Gateway listener\", {\n durationMs,\n mode: this.local ? \"local\" : \"remote\",\n });\n\n this.gatewayOptions = options;\n this.pump.ensureRunning();\n\n const listenerPromise = new Promise<void>((resolve) => {\n const timeout = setTimeout(resolve, durationMs);\n if (abortSignal) {\n const onAbort = () => {\n this.logger.info(\"iMessage Gateway listener received abort signal\");\n clearTimeout(timeout);\n this.pump?.stop();\n resolve();\n };\n if (abortSignal.aborted) {\n onAbort();\n return;\n }\n abortSignal.addEventListener(\"abort\", onAbort, { once: true });\n }\n });\n options.waitUntil(listenerPromise);\n\n return new Response(\n JSON.stringify({\n status: \"listening\",\n durationMs,\n mode: this.local ? \"local\" : \"remote\",\n message: `Gateway listener started, will run for ${durationMs / 1000} seconds`,\n }),\n {\n status: 200,\n headers: { \"Content-Type\": \"application/json\" },\n },\n );\n }\n\n private routeWebhookMessage(\n payload: SpectrumWebhookPayload,\n options?: WebhookOptions,\n ): void {\n if (!this.chat) {\n return;\n }\n\n const { message, space } = payload;\n // Parity with the gateway path: surface only inbound text/attachment\n // messages — skip the bot's own echoes and inbound reactions.\n if (message.direction === \"outbound\") {\n return;\n }\n if (message.content?.type === \"reaction\") {\n return;\n }\n\n const chatMessage = buildChatMessageFromWebhook(message, space);\n this.chat.processMessage(this, chatMessage.threadId, chatMessage, options);\n }\n\n private async routeInbound(\n space: SpectrumSpace,\n message: SpectrumMessage,\n options?: WebhookOptions,\n ): Promise<void> {\n if (!this.chat) {\n return;\n }\n\n this.cache.remember(space, message);\n\n const contentType = message.content.type;\n if (contentType === \"poll_option\") {\n this.handlePollOption(space, message, options);\n return;\n }\n if (contentType === \"reaction\") {\n // Inbound reactions are not surfaced to Chat SDK (parity with the\n // previous adapter, which only forwarded text/attachment messages).\n return;\n }\n if (message.direction === \"outbound\") {\n return;\n }\n\n const chatMessage = buildChatMessage(message, space);\n this.chat.processMessage(this, chatMessage.threadId, chatMessage, options);\n }\n\n private handlePollOption(\n space: SpectrumSpace,\n message: SpectrumMessage,\n options?: WebhookOptions,\n ): void {\n if (!this.chat) {\n return;\n }\n const content = message.content;\n if (content.type !== \"poll_option\") {\n return;\n }\n // Only count a cast vote, not a deselection.\n if (!content.selected) {\n return;\n }\n\n const resolved = this.modals.resolveVote(\n space.id,\n content.poll.title,\n content.option.title,\n );\n if (!resolved) {\n this.logger.debug(\"Poll vote did not match a known modal, skipping\", {\n title: content.poll.title,\n option: content.option.title,\n });\n return;\n }\n\n const { meta, value } = resolved;\n const handle = message.sender?.id ?? \"\";\n\n this.chat.processModalSubmit(\n {\n adapter: this,\n callbackId: meta.callbackId,\n privateMetadata: meta.privateMetadata,\n viewId: meta.viewId,\n user: {\n userId: handle,\n userName: handle,\n fullName: handle,\n isBot: false,\n isMe: false,\n },\n values: { [meta.selectId]: value },\n raw: message,\n },\n meta.contextId,\n options,\n );\n }\n\n /**\n * Resolve a sendable spectrum-ts `Space` for a thread.\n *\n * Prefers a live `Space` cached from the inbound stream (correct sending\n * line, no extra round-trip). On a miss — e.g. a webhook-only deployment, or\n * a cold send — it reconstructs the Space over gRPC via\n * `imessage(app).space([address])`. Only DMs can be rebuilt from a chatGuid:\n * the resolver derives them from the peer address, whereas an unseen group\n * has no by-id resolver (passing participants would create a *new* chat).\n * Returns `undefined` when no Space can be obtained.\n */\n private async resolveSpace(\n threadId: string,\n ): Promise<SpectrumSpace | undefined> {\n const { chatGuid } = decodeThreadId(threadId);\n const cached = this.cache.getSpace(chatGuid);\n if (cached) {\n return cached;\n }\n if (!this.app || this.local || !isDMChatGuid(chatGuid)) {\n return;\n }\n const address = dmAddressFromChatGuid(chatGuid);\n if (!address) {\n return;\n }\n // `HasProvider` over the default provider tuple won't narrow to `true`, so\n // the call types as `never`; cast to the slice of the instance we use.\n const platform = imessage(this.app) as unknown as {\n space(users: string[]): Promise<SpectrumSpace>;\n };\n const space = await platform.space([address]);\n this.cache.rememberSpace(space);\n return space;\n }\n\n private async requireSpace(\n threadId: string,\n action: string,\n ): Promise<SpectrumSpace> {\n const space = await this.resolveSpace(threadId);\n if (!space) {\n throw new NotImplementedError(\n `${action} requires a DM thread (rebuilt from its address) or a group ` +\n \"received in this session; spectrum-ts cannot reconstruct an unseen \" +\n \"group chat from its id. Respond within a received message's thread instead.\",\n action,\n );\n }\n return space;\n }\n\n private async resolveMessage(\n threadId: string,\n messageId: string,\n ): Promise<SpectrumMessage | undefined> {\n const cached = this.cache.getMessage(messageId);\n if (cached) {\n return cached;\n }\n const space = await this.resolveSpace(threadId);\n if (!space) {\n return;\n }\n return (await space.getMessage(messageId)) ?? undefined;\n }\n}\n\nfunction toClientArray(\n clients: IMessageClientEntry | IMessageClientEntry[] | undefined,\n): IMessageClientEntry[] | undefined {\n if (!clients) {\n return;\n }\n return Array.isArray(clients) ? clients : [clients];\n}\n","import { ValidationError } from \"@chat-adapter/shared\";\nimport type { Logger } from \"chat\";\nimport type { IMessageClientEntry } from \"./types\";\n\nexport const SHARED_PHONE = \"shared\";\n\n/** Provider config shape accepted by `imessage.config(...)`. */\nexport type IMessageProviderConfig =\n | { local: true }\n | { clients?: IMessageClientEntry[]; local?: false };\n\nexport interface iMessageAdapterLocalConfig {\n /** Unused in local mode; accepted for symmetry/back-compat. */\n apiKey?: string;\n local: true;\n logger: Logger;\n /** Unused in local mode; accepted for symmetry/back-compat. */\n serverUrl?: string;\n}\n\nexport interface iMessageAdapterRemoteConfig {\n /** Legacy self-host token. Mapped to a `clients` entry's `token`. */\n apiKey?: string;\n /** Explicit self-host gRPC clients (advanced). */\n clients?: IMessageClientEntry | IMessageClientEntry[];\n local: false;\n logger: Logger;\n /** Routing/identity phone for legacy self-host (defaults to `\"shared\"`). */\n phone?: string;\n /** Spectrum Cloud project id (recommended remote path). */\n projectId?: string;\n /** Spectrum Cloud project secret (recommended remote path). */\n projectSecret?: string;\n /** Legacy self-host endpoint. Now a gRPC `host:port` (see README). */\n serverUrl?: string;\n /** Per-webhook signing secret for verifying Spectrum Cloud deliveries. */\n webhookSecret?: string;\n}\n\nexport type iMessageAdapterConfig =\n | iMessageAdapterLocalConfig\n | iMessageAdapterRemoteConfig;\n\nexport interface CreateiMessageAdapterOptions {\n apiKey?: string;\n clients?: IMessageClientEntry | IMessageClientEntry[];\n local?: boolean;\n logger?: Logger;\n phone?: string;\n projectId?: string;\n projectSecret?: string;\n serverUrl?: string;\n webhookSecret?: string;\n}\n\nconst URL_SCHEME_RE = /^[a-z][a-z0-9+.-]*:\\/\\//i;\n\n/**\n * Normalize a legacy `serverUrl` into a gRPC `host:port` address.\n *\n * `@photon-ai/advanced-imessage` (the transport spectrum-ts uses) speaks gRPC,\n * not HTTP/Socket.IO, so any scheme is stripped and a default `:443` port is\n * appended when none is present.\n */\nexport function deriveAddress(serverUrl: string): string {\n const trimmed = serverUrl.trim();\n const hasScheme = URL_SCHEME_RE.test(trimmed);\n // Parse via URL so host/port (including bracketed IPv6) are handled\n // correctly. `URL.hostname` already wraps IPv6 in brackets — don't re-wrap.\n const url = new URL(hasScheme ? trimmed : `https://${trimmed}`);\n return `${url.hostname}:${url.port || \"443\"}`;\n}\n\n/** The resolved remote-auth fields the adapter holds. */\nexport interface RemoteAuth {\n apiKey?: string;\n clients?: IMessageClientEntry[];\n phone?: string;\n projectId?: string;\n projectSecret?: string;\n serverUrl?: string;\n}\n\n/**\n * Translate the adapter's stored config into the `imessage.config(...)` payload\n * plus any Spectrum Cloud credentials.\n */\nexport function resolveSpectrumConfig(\n local: boolean,\n auth: RemoteAuth\n): {\n projectId?: string;\n projectSecret?: string;\n providerConfig: IMessageProviderConfig;\n} {\n if (local) {\n return { providerConfig: { local: true } };\n }\n if (auth.projectId && auth.projectSecret) {\n return {\n providerConfig: {},\n projectId: auth.projectId,\n projectSecret: auth.projectSecret,\n };\n }\n if (auth.clients?.length) {\n return { providerConfig: { clients: auth.clients } };\n }\n if (auth.serverUrl && auth.apiKey) {\n return {\n providerConfig: {\n clients: [\n {\n address: deriveAddress(auth.serverUrl),\n token: auth.apiKey,\n phone: auth.phone ?? SHARED_PHONE,\n },\n ],\n },\n };\n }\n throw new ValidationError(\n \"imessage\",\n \"Remote mode requires Spectrum Cloud credentials (projectId + projectSecret), explicit clients, or serverUrl + apiKey.\"\n );\n}\n","import type {\n Message as SpectrumMessage,\n Space as SpectrumSpace,\n} from \"spectrum-ts\";\n\nconst DEFAULT_MAX_SPACES = 256;\nconst DEFAULT_MAX_MESSAGES = 1024;\n\n/**\n * Bounded, insertion-order (FIFO-evicted) cache of the Spaces and Messages seen\n * on the inbound stream. The stateless, threadId-addressed Adapter methods\n * (`postMessage`, `addReaction`, …) resolve their target Space/Message from\n * here — spectrum-ts can construct neither from a bare id.\n */\nexport class InboundCache {\n private readonly spaces = new Map<string, SpectrumSpace>();\n private readonly messages = new Map<string, SpectrumMessage>();\n private readonly maxSpaces: number;\n private readonly maxMessages: number;\n\n constructor(\n maxSpaces = DEFAULT_MAX_SPACES,\n maxMessages = DEFAULT_MAX_MESSAGES\n ) {\n this.maxSpaces = maxSpaces;\n this.maxMessages = maxMessages;\n }\n\n /** Cache the Space + Message (and any group sub-items) from one inbound event. */\n remember(space: SpectrumSpace, message: SpectrumMessage): void {\n this.rememberSpace(space);\n this.rememberMessage(message);\n if (message.content.type === \"group\") {\n for (const item of message.content.items) {\n this.rememberMessage(item);\n }\n }\n }\n\n rememberSpace(space: SpectrumSpace): void {\n this.spaces.set(space.id, space);\n evict(this.spaces, this.maxSpaces);\n }\n\n rememberMessage(message: SpectrumMessage): void {\n this.messages.set(message.id, message);\n evict(this.messages, this.maxMessages);\n }\n\n getSpace(chatGuid: string): SpectrumSpace | undefined {\n return this.spaces.get(chatGuid);\n }\n\n getMessage(id: string): SpectrumMessage | undefined {\n return this.messages.get(id);\n }\n}\n\nfunction evict(map: Map<string, unknown>, max: number): void {\n if (map.size <= max) {\n return;\n }\n const overflow = map.size - max;\n let removed = 0;\n for (const key of map.keys()) {\n if (removed >= overflow) {\n break;\n }\n map.delete(key);\n removed += 1;\n }\n}\n","import type { Logger } from \"chat\";\nimport type {\n Message as SpectrumMessage,\n Space as SpectrumSpace,\n} from \"spectrum-ts\";\n\ntype InboundTuple = [SpectrumSpace, SpectrumMessage];\n\ntype OnMessage = (\n space: SpectrumSpace,\n message: SpectrumMessage\n) => Promise<void>;\n\n/**\n * A single, long-lived consumer of spectrum-ts's `app.messages` stream. One\n * persistent pump (rather than a fresh subscription per gateway call) avoids\n * dropping an in-flight message on timeout and keeps the connection warm across\n * overlapping cron windows.\n */\nexport class MessagePump {\n private started = false;\n private iterator: AsyncIterator<InboundTuple> | null = null;\n private readonly source: () => AsyncIterable<InboundTuple>;\n private readonly onMessage: OnMessage;\n private readonly logger: Logger;\n\n constructor(\n source: () => AsyncIterable<InboundTuple>,\n onMessage: OnMessage,\n logger: Logger\n ) {\n this.source = source;\n this.onMessage = onMessage;\n this.logger = logger;\n }\n\n /** Start consuming if not already running. Idempotent. */\n ensureRunning(): void {\n if (this.started) {\n return;\n }\n this.started = true;\n\n const iterator = this.source()[Symbol.asyncIterator]();\n this.iterator = iterator;\n\n this.consume(iterator).catch(() => {\n // consume() handles its own errors; this is an unreachable safety net.\n });\n }\n\n /** Close the underlying stream and stop consuming. */\n stop(): void {\n const iterator = this.iterator;\n this.iterator = null;\n this.started = false;\n iterator?.return?.().catch(() => {\n // ignore teardown errors\n });\n }\n\n private async consume(iterator: AsyncIterator<InboundTuple>): Promise<void> {\n try {\n while (true) {\n const next = await iterator.next();\n if (next.done) {\n break;\n }\n const [space, message] = next.value;\n try {\n await this.onMessage(space, message);\n } catch (error) {\n this.logger.error(\"iMessage inbound handler error\", {\n error: String(error),\n });\n }\n }\n } catch (error) {\n this.logger.error(\"iMessage message stream error\", {\n error: String(error),\n });\n } finally {\n // Reset so a future ensureRunning() can restart the pump if the stream\n // ended/threw on its own. Guard against clobbering a newer iterator\n // installed by a concurrent restart.\n if (this.iterator === iterator) {\n this.iterator = null;\n this.started = false;\n }\n this.logger.info(\"iMessage Gateway listener stopped\");\n }\n }\n}\n","import { Message, parseMarkdown } from \"chat\";\nimport type {\n Content as SpectrumContent,\n Message as SpectrumMessage,\n Space as SpectrumSpace,\n} from \"spectrum-ts\";\nimport { encodeThreadId, isDMChatGuid } from \"./thread\";\n\n/**\n * The provider-agnostic fields a Chat SDK `Message` is built from. Both the\n * gateway (live spectrum-ts `Message`) and the webhook (plain JSON delivery)\n * paths normalize down to this shape.\n */\nexport interface InboundMessageFields {\n chatGuid: string;\n content: SpectrumContent;\n id: string;\n isOutbound: boolean;\n raw: unknown;\n senderId: string;\n timestamp: Date;\n}\n\n/** Build the Chat SDK `Message` from already-normalized inbound fields. */\nexport function buildChatMessageFromFields(\n fields: InboundMessageFields\n): Message {\n const text = extractText(fields.content);\n\n return new Message({\n id: fields.id,\n threadId: encodeThreadId({ chatGuid: fields.chatGuid }),\n text,\n formatted: parseMarkdown(text),\n author: {\n userId: fields.senderId,\n userName: fields.senderId,\n fullName: fields.senderId,\n isBot: false,\n isMe: fields.isOutbound,\n },\n metadata: { dateSent: fields.timestamp, edited: false },\n attachments: extractAttachments(fields.content).map((a) => ({\n type: getAttachmentType(a.mimeType),\n name: a.name,\n mimeType: a.mimeType,\n size: a.size ?? 0,\n })),\n raw: fields.raw,\n isMention: isDMChatGuid(fields.chatGuid),\n });\n}\n\n/** Build the Chat SDK `Message` the adapter surfaces from a spectrum-ts Message. */\nexport function buildChatMessage(\n message: SpectrumMessage,\n space: SpectrumSpace\n): Message {\n return buildChatMessageFromFields({\n id: message.id,\n chatGuid: space.id,\n content: message.content,\n senderId: message.sender?.id ?? \"\",\n isOutbound: message.direction === \"outbound\",\n timestamp: message.timestamp,\n raw: message,\n });\n}\n\nfunction extractText(content: SpectrumContent): string {\n switch (content.type) {\n case \"text\":\n return content.text;\n case \"richlink\":\n return String(content.url);\n case \"poll\":\n return content.title;\n case \"group\":\n return content.items\n .map((item) => extractText(item.content))\n .filter((t) => t.length > 0)\n .join(\"\\n\");\n default:\n return \"\";\n }\n}\n\ninterface ExtractedAttachment {\n mimeType: string;\n name: string;\n size?: number;\n}\n\nfunction extractAttachments(content: SpectrumContent): ExtractedAttachment[] {\n const out: ExtractedAttachment[] = [];\n const visit = (c: SpectrumContent): void => {\n if (c.type === \"attachment\") {\n out.push({ name: c.name, mimeType: c.mimeType, size: c.size });\n } else if (c.type === \"voice\") {\n const voice = c as { mimeType: string; name?: string; size?: number };\n out.push({\n name: voice.name ?? \"voice\",\n mimeType: voice.mimeType,\n size: voice.size,\n });\n } else if (c.type === \"group\") {\n for (const item of c.items) {\n visit(item.content);\n }\n }\n };\n visit(content);\n return out;\n}\n\nfunction getAttachmentType(\n mimeType?: string\n): \"image\" | \"video\" | \"audio\" | \"file\" {\n if (!mimeType) {\n return \"file\";\n }\n if (mimeType.startsWith(\"image/\")) {\n return \"image\";\n }\n if (mimeType.startsWith(\"video/\")) {\n return \"video\";\n }\n if (mimeType.startsWith(\"audio/\")) {\n return \"audio\";\n }\n return \"file\";\n}\n","import { ValidationError } from \"@chat-adapter/shared\";\nimport type { iMessageThreadId } from \"../types\";\n\nconst THREAD_PREFIX = \"imessage:\";\n\nexport function encodeThreadId(platformData: iMessageThreadId): string {\n return `${THREAD_PREFIX}${platformData.chatGuid}`;\n}\n\nexport function decodeThreadId(threadId: string): iMessageThreadId {\n if (!threadId.startsWith(THREAD_PREFIX)) {\n throw new ValidationError(\n \"imessage\",\n `Invalid iMessage thread ID: ${threadId}`\n );\n }\n const chatGuid = threadId.slice(THREAD_PREFIX.length);\n if (!chatGuid) {\n throw new ValidationError(\n \"imessage\",\n `Invalid iMessage thread ID: ${threadId} (empty chat GUID)`\n );\n }\n return { chatGuid };\n}\n\n/** DM chat GUIDs use the `;-;` separator; group chats use `;+;`. */\nexport function isDMChatGuid(chatGuid: string): boolean {\n return chatGuid.includes(\";-;\");\n}\n\nconst DM_SEPARATOR = \";-;\";\n\n/**\n * Extract the peer address from a DM chat GUID (`<service>;-;<address>`, e.g.\n * `any;-;+15550100`). Returns `\"\"` for a non-DM GUID. spectrum-ts rebuilds a\n * sendable DM `Space` from this address — `imessage(app).space([address])`.\n */\nexport function dmAddressFromChatGuid(chatGuid: string): string {\n const idx = chatGuid.indexOf(DM_SEPARATOR);\n return idx === -1 ? \"\" : chatGuid.slice(idx + DM_SEPARATOR.length);\n}\n","import type { ModalPollMeta } from \"../types\";\n\nconst DEFAULT_MAX_MODALS = 512;\n\nexport interface ResolvedVote {\n meta: ModalPollMeta;\n value: string;\n}\n\n/**\n * Tracks Chat SDK modals that were rendered as iMessage native polls and maps\n * inbound votes back to them.\n *\n * iMessage `poll_option` votes carry no poll GUID — only the poll title and the\n * chosen option's title — so votes are matched by `${chatGuid}::${pollTitle}`\n * and the Chat SDK option `value` is recovered from the stored option list.\n *\n * Entries are retained (not deleted on the first vote) so multi-participant\n * polls keep resolving, and bounded with FIFO eviction so the map cannot grow\n * without bound over the adapter's lifetime.\n */\nexport class ModalPollRegistry {\n private readonly byTitle = new Map<string, ModalPollMeta>();\n private readonly maxEntries: number;\n\n constructor(maxEntries = DEFAULT_MAX_MODALS) {\n this.maxEntries = maxEntries;\n }\n\n register(chatGuid: string, title: string, meta: ModalPollMeta): void {\n const key = titleKey(chatGuid, title);\n // Re-registering moves the key to the most-recent position.\n this.byTitle.delete(key);\n this.byTitle.set(key, meta);\n if (this.byTitle.size > this.maxEntries) {\n const oldest = this.byTitle.keys().next().value;\n if (oldest !== undefined) {\n this.byTitle.delete(oldest);\n }\n }\n }\n\n resolveVote(\n chatGuid: string,\n pollTitle: string,\n optionTitle: string\n ): ResolvedVote | undefined {\n const meta = this.byTitle.get(titleKey(chatGuid, pollTitle));\n if (!meta) {\n return;\n }\n const option = meta.options.find((o) => o.label === optionTitle);\n if (!option) {\n // Unknown option label — don't fabricate an unregistered submit value.\n return;\n }\n return { meta, value: option.value };\n }\n}\n\nfunction titleKey(chatGuid: string, title: string): string {\n return `${chatGuid}::${title}`;\n}\n","import { ValidationError } from \"@chat-adapter/shared\";\nimport type { EmojiValue, FileUpload } from \"chat\";\nimport { lookup as lookupMimeType } from \"mime-types\";\nimport { attachment, type ContentBuilder } from \"spectrum-ts\";\n\nconst EMOJI_GLYPHS: Record<string, string> = {\n heart: \"❤️\",\n love: \"❤️\",\n thumbs_up: \"👍\",\n like: \"👍\",\n thumbs_down: \"👎\",\n dislike: \"👎\",\n laugh: \"😂\",\n emphasize: \"‼️\",\n exclamation: \"‼️\",\n question: \"❓\",\n};\n\n/**\n * Map a Chat SDK emoji (name or `EmojiValue`) to the glyph spectrum-ts maps to\n * an iMessage tapback. Throws on an unsupported emoji.\n */\nexport function emojiToGlyph(emoji: EmojiValue | string): string {\n const name = typeof emoji === \"string\" ? emoji : emoji.name;\n const glyph = EMOJI_GLYPHS[name];\n if (!glyph) {\n throw new ValidationError(\n \"imessage\",\n `Unsupported iMessage tapback: \"${name}\". Supported: heart, thumbs_up, thumbs_down, laugh, emphasize, question`\n );\n }\n return glyph;\n}\n\n/** Convert a Chat SDK file upload into a spectrum-ts attachment content builder. */\nexport async function fileToAttachment(\n file: FileUpload\n): Promise<ContentBuilder> {\n const data = file.data;\n let buffer: Buffer;\n if (Buffer.isBuffer(data)) {\n buffer = data;\n } else if (data instanceof Blob) {\n buffer = Buffer.from(await data.arrayBuffer());\n } else {\n buffer = Buffer.from(data as ArrayBuffer);\n }\n\n const name = file.filename || \"attachment\";\n // Always resolve an explicit MIME type: spectrum's attachment() throws if it\n // can't infer one from `name` (no octet-stream fallback, and dotted-but-\n // unknown extensions still miss). Use the upload's type, else infer from the\n // extension, else a generic binary type.\n const mimeType =\n file.mimeType || lookupMimeType(name) || \"application/octet-stream\";\n\n return attachment(buffer, { name, mimeType });\n}\n","import { createHmac, timingSafeEqual } from \"node:crypto\";\nimport type { Message } from \"chat\";\nimport type { Content as SpectrumContent } from \"spectrum-ts\";\nimport { buildChatMessageFromFields } from \"./inbound\";\n\n/**\n * Spectrum Cloud webhook delivery. Spectrum's service POSTs message events to a\n * registered URL as JSON, signed with a per-webhook secret.\n *\n * See https://photon.codes/docs/webhooks/overview\n */\n\n/** Lowercase header names — `Headers.get` is case-insensitive per the spec. */\nexport const SPECTRUM_SIGNATURE_HEADER = \"x-spectrum-signature\";\nexport const SPECTRUM_TIMESTAMP_HEADER = \"x-spectrum-timestamp\";\nexport const SPECTRUM_EVENT_HEADER = \"x-spectrum-event\";\n\n/** The only event Spectrum Cloud delivers today. */\nexport const SPECTRUM_MESSAGES_EVENT = \"messages\";\n\n/** Reject deliveries whose timestamp drifts more than this from now (replay guard). */\nconst TIMESTAMP_TOLERANCE_SEC = 5 * 60;\n\nexport interface SpectrumWebhookSpace {\n id: string;\n phone?: string;\n platform?: string;\n type?: \"dm\" | \"group\";\n}\n\nexport interface SpectrumWebhookMessage {\n content: { type?: string; [key: string]: unknown };\n direction?: \"inbound\" | \"outbound\";\n id: string;\n platform?: string;\n sender?: { id: string; platform?: string };\n space?: SpectrumWebhookSpace;\n timestamp?: string;\n}\n\nexport interface SpectrumWebhookPayload {\n event: string;\n message: SpectrumWebhookMessage;\n space: SpectrumWebhookSpace;\n}\n\nexport type SignatureVerification =\n | { ok: true }\n | { ok: false; reason: string; status: number };\n\n/**\n * Verify a Spectrum Cloud webhook's HMAC-SHA256 signature.\n *\n * The signing base is `v0:{timestamp}:{rawBody}` and the header value is\n * `v0=<lowercase hex>`. `rawBody` MUST be the exact bytes received — verify\n * before parsing JSON.\n */\nexport function verifySpectrumSignature(opts: {\n now?: number;\n rawBody: string;\n secret: string;\n signature: string | null;\n timestamp: string | null;\n}): SignatureVerification {\n const { rawBody, secret, signature, timestamp } = opts;\n if (!(signature && timestamp)) {\n return { ok: false, status: 400, reason: \"missing signature headers\" };\n }\n\n const nowSec = opts.now ?? Math.floor(Date.now() / 1000);\n const age = Math.abs(nowSec - Number(timestamp));\n if (!Number.isFinite(age) || age > TIMESTAMP_TOLERANCE_SEC) {\n return { ok: false, status: 400, reason: \"stale or invalid timestamp\" };\n }\n\n const expected = `v0=${createHmac(\"sha256\", secret)\n .update(`v0:${timestamp}:${rawBody}`)\n .digest(\"hex\")}`;\n\n // Constant-time compare; lengths must match first or timingSafeEqual throws.\n const a = Buffer.from(expected);\n const b = Buffer.from(signature);\n if (a.length !== b.length || !timingSafeEqual(a, b)) {\n return { ok: false, status: 401, reason: \"bad signature\" };\n }\n\n return { ok: true };\n}\n\n/**\n * Build the Chat SDK `Message` from a Spectrum Cloud webhook delivery.\n *\n * The wire format is the spectrum-ts `Content` union with methods stripped, so\n * the message `content` is structurally compatible with the inbound content\n * extractors (which only read metadata fields).\n */\nexport function buildChatMessageFromWebhook(\n message: SpectrumWebhookMessage,\n space: SpectrumWebhookSpace\n): Message {\n return buildChatMessageFromFields({\n id: message.id,\n chatGuid: space.id,\n content: message.content as unknown as SpectrumContent,\n senderId: message.sender?.id ?? \"\",\n isOutbound: message.direction === \"outbound\",\n timestamp: message.timestamp ? new Date(message.timestamp) : new Date(),\n raw: message,\n });\n}\n","/**\n * iMessage format conversion using AST-based parsing.\n *\n * iMessage supports plain text only -- no rich formatting syntax.\n * The converter strips formatting markers and outputs clean plain text,\n * preserving structure (lists, blockquotes, code blocks) with whitespace.\n */\n\nimport {\n BaseFormatConverter,\n type Content,\n getNodeChildren,\n getNodeValue,\n isBlockquoteNode,\n isCodeNode,\n isDeleteNode,\n isEmphasisNode,\n isInlineCodeNode,\n isLinkNode,\n isListItemNode,\n isListNode,\n isParagraphNode,\n isStrongNode,\n isTextNode,\n parseMarkdown,\n type Root,\n} from \"chat\";\n\nexport class iMessageFormatConverter extends BaseFormatConverter {\n /**\n * Render an AST to iMessage plain text format.\n * Strips all formatting markers since iMessage doesn't support rich text via API.\n */\n fromAst(ast: Root): string {\n return this.fromAstWithNodeConverter(ast, (node) =>\n this.nodeToPlainText(node)\n );\n }\n\n /**\n * Parse iMessage text into an AST.\n * iMessage sends plain text, so we just parse it as markdown.\n */\n toAst(text: string): Root {\n return parseMarkdown(text);\n }\n\n private nodeToPlainText(node: Content): string {\n if (isParagraphNode(node)) {\n return getNodeChildren(node)\n .map((child) => this.nodeToPlainText(child))\n .join(\"\");\n }\n\n if (isTextNode(node)) {\n return node.value;\n }\n\n if (isStrongNode(node) || isEmphasisNode(node) || isDeleteNode(node)) {\n return getNodeChildren(node)\n .map((child) => this.nodeToPlainText(child))\n .join(\"\");\n }\n\n if (isInlineCodeNode(node)) {\n return node.value;\n }\n\n if (isCodeNode(node)) {\n return node.value;\n }\n\n if (isLinkNode(node)) {\n const linkText = getNodeChildren(node)\n .map((child) => this.nodeToPlainText(child))\n .join(\"\");\n return linkText ? `${linkText} (${node.url})` : node.url;\n }\n\n if (isBlockquoteNode(node)) {\n return getNodeChildren(node)\n .map((child) => `> ${this.nodeToPlainText(child)}`)\n .join(\"\\n\");\n }\n\n if (isListNode(node)) {\n return getNodeChildren(node)\n .map((item, i) => {\n const prefix = node.ordered ? `${i + 1}.` : \"-\";\n const content = getNodeChildren(item)\n .map((child) => this.nodeToPlainText(child))\n .join(\"\");\n return `${prefix} ${content}`;\n })\n .join(\"\\n\");\n }\n\n if (isListItemNode(node)) {\n return getNodeChildren(node)\n .map((child) => this.nodeToPlainText(child))\n .join(\"\");\n }\n\n if (node.type === \"break\") {\n return \"\\n\";\n }\n\n if (node.type === \"thematicBreak\") {\n return \"---\";\n }\n\n const children = getNodeChildren(node);\n if (children.length > 0) {\n return children.map((child) => this.nodeToPlainText(child)).join(\"\");\n }\n return getNodeValue(node);\n }\n}\n","import { ValidationError } from \"@chat-adapter/shared\";\nimport { ConsoleLogger } from \"chat\";\nimport { iMessageAdapter } from \"./adapter\";\nimport type { CreateiMessageAdapterOptions } from \"./config\";\n\n/**\n * Decide local vs remote: an explicit local signal (`config.local`, then\n * `IMESSAGE_LOCAL`) wins; otherwise pick remote when remote credentials are\n * present, else local.\n */\nfunction resolveLocalMode(\n explicit: boolean | undefined,\n hasRemoteCreds: boolean\n): boolean {\n if (explicit !== undefined) {\n return explicit;\n }\n const env = process.env.IMESSAGE_LOCAL;\n if (env === \"false\") {\n return false;\n }\n if (env === undefined) {\n return !hasRemoteCreds;\n }\n return true;\n}\n\nfunction assertRemoteCredentials(creds: {\n hasApiKey: boolean;\n hasClients: boolean;\n hasCloud: boolean;\n hasServerUrl: boolean;\n}): void {\n if (creds.hasCloud || creds.hasClients) {\n return;\n }\n if (!creds.hasServerUrl) {\n throw new ValidationError(\n \"imessage\",\n \"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.\"\n );\n }\n if (!creds.hasApiKey) {\n throw new ValidationError(\n \"imessage\",\n \"apiKey is required when local is false. Set IMESSAGE_API_KEY or provide it in config.\"\n );\n }\n}\n\n/**\n * Construct an {@link iMessageAdapter}, filling unset options from environment\n * variables.\n *\n * Mode is chosen by an explicit local signal first — `config.local`, then\n * `IMESSAGE_LOCAL` — otherwise remote when remote credentials are present (cloud\n * `projectId` + `projectSecret`, or self-host `clients` / `serverUrl` +\n * `apiKey`), else local.\n */\nexport function createiMessageAdapter(\n config?: CreateiMessageAdapterOptions\n): iMessageAdapter {\n const logger = config?.logger ?? new ConsoleLogger(\"info\").child(\"imessage\");\n\n const projectId = config?.projectId ?? process.env.IMESSAGE_PROJECT_ID;\n const projectSecret =\n config?.projectSecret ?? process.env.IMESSAGE_PROJECT_SECRET;\n const clients = config?.clients;\n // Normalize once: trim so surrounding whitespace neither passes validation\n // nor reaches the adapter (where it would break the gRPC address/token).\n const serverUrl = (\n config?.serverUrl ?? process.env.IMESSAGE_SERVER_URL\n )?.trim();\n const apiKey = (config?.apiKey ?? process.env.IMESSAGE_API_KEY)?.trim();\n const phone = config?.phone ?? process.env.IMESSAGE_PHONE;\n const webhookSecret = (\n config?.webhookSecret ?? process.env.IMESSAGE_WEBHOOK_SECRET\n )?.trim();\n\n const hasClients = Array.isArray(clients)\n ? clients.length > 0\n : Boolean(clients);\n const hasCloud = Boolean(projectId && projectSecret);\n const hasServerUrl = Boolean(serverUrl);\n const hasApiKey = Boolean(apiKey);\n const hasRemoteCreds = hasCloud || hasClients || (hasServerUrl && hasApiKey);\n\n if (resolveLocalMode(config?.local, hasRemoteCreds)) {\n return new iMessageAdapter({ local: true, logger, serverUrl, apiKey });\n }\n\n assertRemoteCredentials({ hasApiKey, hasClients, hasCloud, hasServerUrl });\n\n return new iMessageAdapter({\n local: false,\n logger,\n projectId,\n projectSecret,\n clients,\n serverUrl,\n apiKey,\n phone,\n webhookSecret,\n });\n}\n"],"mappings":"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","names":["ValidationError","ValidationError","ValidationError","parseMarkdown","ValidationError","ValidationError","ValidationError"]}
|
|
1
|
+
{"version":3,"sources":["../src/adapter.ts","../src/config.ts","../src/internal/cache.ts","../src/internal/gateway.ts","../src/internal/inbound.ts","../src/internal/thread.ts","../src/internal/modals.ts","../src/internal/outbound.ts","../src/internal/webhook.ts","../src/markdown.ts","../src/factory.ts"],"sourcesContent":["import { extractFiles, ValidationError } from \"@chat-adapter/shared\";\nimport type {\n Adapter,\n AdapterPostableMessage,\n ChatInstance,\n EmojiValue,\n FetchOptions,\n FetchResult,\n FormattedContent,\n Logger,\n Message,\n ModalElement,\n RawMessage,\n SelectElement,\n ThreadInfo,\n WebhookOptions,\n} from \"chat\";\nimport { NotImplementedError } from \"chat\";\nimport {\n poll as pollContent,\n Spectrum,\n type SpectrumInstance,\n type Message as SpectrumMessage,\n type Space as SpectrumSpace,\n text as textContent,\n} from \"spectrum-ts\";\nimport { imessage } from \"spectrum-ts/providers/imessage\";\nimport { type iMessageAdapterConfig, resolveSpectrumConfig } from \"./config\";\nimport { InboundCache } from \"./internal/cache\";\nimport { MessagePump } from \"./internal/gateway\";\nimport { buildChatMessage } from \"./internal/inbound\";\nimport { ModalPollRegistry } from \"./internal/modals\";\nimport { emojiToGlyph, fileToAttachment } from \"./internal/outbound\";\nimport {\n decodeThreadId,\n encodeThreadId,\n isDMChatGuid,\n} from \"./internal/thread\";\nimport {\n buildChatMessageFromWebhook,\n SPECTRUM_EVENT_HEADER,\n SPECTRUM_MESSAGES_EVENT,\n SPECTRUM_SIGNATURE_HEADER,\n SPECTRUM_TIMESTAMP_HEADER,\n type SpectrumWebhookPayload,\n verifySpectrumSignature,\n} from \"./internal/webhook\";\nimport { iMessageFormatConverter } from \"./markdown\";\nimport type { IMessageClientEntry, iMessageThreadId } from \"./types\";\n\nconst TYPING_DURATION_MS = 3000;\n\nexport class iMessageAdapter implements Adapter {\n readonly name = \"imessage\";\n readonly userName: string = \"\";\n readonly local: boolean;\n readonly serverUrl?: string;\n readonly apiKey?: string;\n readonly projectId?: string;\n readonly projectSecret?: string;\n readonly clients?: IMessageClientEntry[];\n readonly phone?: string;\n readonly webhookSecret?: string;\n\n /** The spectrum-ts instance — null until `initialize()` runs. */\n app: SpectrumInstance | null = null;\n\n private chat: ChatInstance | null = null;\n private readonly logger: Logger;\n private readonly formatConverter = new iMessageFormatConverter();\n private readonly cache = new InboundCache();\n private readonly modals = new ModalPollRegistry();\n\n private gatewayOptions?: WebhookOptions;\n private pump: MessagePump | null = null;\n\n constructor(config: iMessageAdapterConfig) {\n if (config.local && process.platform !== \"darwin\") {\n throw new ValidationError(\n \"imessage\",\n \"iMessage adapter local mode requires macOS. Current platform: \" +\n process.platform\n );\n }\n\n this.local = config.local;\n this.logger = config.logger;\n this.serverUrl = config.serverUrl;\n this.apiKey = config.apiKey;\n\n if (!config.local) {\n this.projectId = config.projectId;\n this.projectSecret = config.projectSecret;\n this.phone = config.phone;\n this.clients = toClientArray(config.clients);\n // Trim here too so direct `new iMessageAdapter(...)` matches the factory\n // (createiMessageAdapter trims it): a stray space would otherwise fail\n // signature verification only on the constructor path.\n this.webhookSecret = config.webhookSecret?.trim();\n }\n }\n\n async initialize(chat: ChatInstance): Promise<void> {\n this.chat = chat;\n\n const { providerConfig, projectId, projectSecret } = resolveSpectrumConfig(\n this.local,\n {\n apiKey: this.apiKey,\n clients: this.clients,\n phone: this.phone,\n projectId: this.projectId,\n projectSecret: this.projectSecret,\n serverUrl: this.serverUrl,\n }\n );\n const providers = [imessage.config(providerConfig)];\n\n this.app =\n projectId && projectSecret\n ? await Spectrum({ providers, projectId, projectSecret })\n : await Spectrum({ providers });\n\n this.pump = new MessagePump(\n () => {\n if (!this.app) {\n throw new Error(\"Adapter not initialized\");\n }\n return this.app.messages;\n },\n (space, message) =>\n this.routeInbound(space, message, this.gatewayOptions),\n this.logger\n );\n\n let mode: \"local\" | \"cloud\" | \"self-host\";\n if (this.local) {\n mode = \"local\";\n } else if (projectId) {\n mode = \"cloud\";\n } else {\n mode = \"self-host\";\n }\n this.logger.info(\"iMessage adapter initialized\", {\n local: this.local,\n mode,\n });\n }\n\n /**\n * Handle a Spectrum Cloud webhook delivery (signed JSON `messages` event).\n *\n * Verifies the `X-Spectrum-Signature` HMAC, then routes the message into the\n * Chat SDK. A delivered thread has no live spectrum-ts `Space`, but the\n * adapter rebuilds one from the chat GUID on demand (see `resolveSpace`), so\n * replying works directly from a webhook delivery.\n *\n * @see https://photon.codes/docs/webhooks/overview\n */\n async handleWebhook(\n request: Request,\n options?: WebhookOptions\n ): Promise<Response> {\n if (!this.chat) {\n return new Response(\"Chat instance not initialized\", { status: 500 });\n }\n if (this.local) {\n return new Response(\n \"Webhooks require remote (cloud) mode — local mode receives via startGatewayListener()\",\n { status: 501 }\n );\n }\n if (!this.webhookSecret) {\n return new Response(\n \"Webhook signing secret not configured (set IMESSAGE_WEBHOOK_SECRET)\",\n { status: 500 }\n );\n }\n\n // Read the raw body BEFORE parsing: the signature covers the exact bytes.\n const rawBody = await request.text();\n const verdict = verifySpectrumSignature({\n secret: this.webhookSecret,\n signature: request.headers.get(SPECTRUM_SIGNATURE_HEADER),\n timestamp: request.headers.get(SPECTRUM_TIMESTAMP_HEADER),\n rawBody,\n });\n if (!verdict.ok) {\n this.logger.warn(\"Rejected iMessage webhook delivery\", {\n reason: verdict.reason,\n });\n return new Response(verdict.reason, { status: verdict.status });\n }\n\n const event = request.headers.get(SPECTRUM_EVENT_HEADER);\n if (event && event !== SPECTRUM_MESSAGES_EVENT) {\n // Acknowledge unrecognized event types so Cloud does not retry them.\n return new Response(null, { status: 204 });\n }\n\n let payload: SpectrumWebhookPayload;\n try {\n payload = JSON.parse(rawBody) as SpectrumWebhookPayload;\n } catch {\n return new Response(\"Invalid JSON body\", { status: 400 });\n }\n\n if (\n payload.event !== SPECTRUM_MESSAGES_EVENT ||\n !(payload.message && payload.space)\n ) {\n return new Response(null, { status: 204 });\n }\n\n this.routeWebhookMessage(payload, options);\n return new Response(null, { status: 200 });\n }\n\n async postMessage(\n threadId: string,\n message: AdapterPostableMessage\n ): Promise<RawMessage> {\n const space = await this.requireSpace(threadId, \"postMessage\");\n const body = this.formatConverter.renderPostable(message);\n const files = extractFiles(message);\n\n let first: SpectrumMessage | undefined;\n if (body && body.trim().length > 0) {\n first = (await space.send(textContent(body))) ?? first;\n }\n for (const file of files) {\n const sent =\n (await space.send(await fileToAttachment(file))) ?? undefined;\n first ??= sent;\n }\n\n if (!first) {\n throw new ValidationError(\n \"imessage\",\n \"postMessage requires non-empty text or at least one attachment\"\n );\n }\n\n return { id: first.id, threadId, raw: first };\n }\n\n async editMessage(\n threadId: string,\n messageId: string,\n message: AdapterPostableMessage\n ): Promise<RawMessage> {\n if (this.local) {\n throw new NotImplementedError(\n \"editMessage is not supported in local mode\",\n \"editMessage\"\n );\n }\n\n const target = await this.resolveMessage(threadId, messageId);\n if (!target) {\n throw new NotImplementedError(\n \"editMessage requires the original message to have been received in this session\",\n \"editMessage\"\n );\n }\n\n await target.edit(\n textContent(this.formatConverter.renderPostable(message))\n );\n return { id: messageId, threadId, raw: target };\n }\n\n async deleteMessage(_threadId: string, _messageId: string): Promise<void> {\n throw new NotImplementedError(\n \"deleteMessage is not implemented\",\n \"deleteMessage\"\n );\n }\n\n parseMessage(raw: unknown): Message {\n const message = raw as SpectrumMessage;\n return buildChatMessage(message, message.space);\n }\n\n async fetchMessages(\n _threadId: string,\n _options?: FetchOptions\n ): Promise<FetchResult> {\n throw new NotImplementedError(\n \"fetchMessages (message history) is not supported by spectrum-ts\",\n \"fetchMessages\"\n );\n }\n\n async fetchThread(_threadId: string): Promise<ThreadInfo> {\n throw new NotImplementedError(\n \"fetchThread (chat info) is not supported by spectrum-ts\",\n \"fetchThread\"\n );\n }\n\n channelIdFromThreadId(threadId: string): string {\n return threadId;\n }\n\n async addReaction(\n threadId: string,\n messageId: string,\n emoji: EmojiValue | string\n ): Promise<void> {\n if (this.local) {\n throw new NotImplementedError(\n \"addReaction is not supported in local mode\",\n \"addReaction\"\n );\n }\n\n const glyph = emojiToGlyph(emoji);\n const target = await this.resolveMessage(threadId, messageId);\n if (!target) {\n throw new NotImplementedError(\n \"addReaction requires the target message to have been received in this session\",\n \"addReaction\"\n );\n }\n\n await target.react(glyph);\n }\n\n async removeReaction(\n _threadId: string,\n _messageId: string,\n _emoji: EmojiValue | string\n ): Promise<void> {\n throw new NotImplementedError(\n \"removeReaction is not supported (spectrum-ts has no reaction-removal API)\",\n \"removeReaction\"\n );\n }\n\n async startTyping(threadId: string, _status?: string): Promise<void> {\n if (this.local) {\n throw new NotImplementedError(\n \"startTyping is not supported in local mode\",\n \"startTyping\"\n );\n }\n\n const space = await this.requireSpace(threadId, \"startTyping\");\n await space.startTyping();\n setTimeout(() => {\n space.stopTyping().catch(() => {\n // best-effort; ignore failures\n });\n }, TYPING_DURATION_MS);\n }\n\n async openModal(\n triggerId: string,\n modal: ModalElement,\n contextId?: string\n ): Promise<{ viewId: string }> {\n if (this.local) {\n throw new NotImplementedError(\n \"openModal is not supported in local mode\",\n \"openModal\"\n );\n }\n\n const select = modal.children.find(\n (c): c is SelectElement => c.type === \"select\"\n );\n if (!select) {\n throw new ValidationError(\n \"imessage\",\n \"openModal requires at least one Select child — iMessage modals map to native polls\"\n );\n }\n\n const labels = select.options.map((o) => o.label);\n if (labels.length < 2 || labels.length > 10) {\n throw new ValidationError(\n \"imessage\",\n `iMessage polls require between 2 and 10 options, received ${labels.length}`\n );\n }\n\n const { chatGuid } = decodeThreadId(triggerId);\n const space = await this.requireSpace(triggerId, \"openModal\");\n\n const sent = await space.send(pollContent(modal.title, labels));\n const viewId = sent?.id ?? `poll-${Date.now()}`;\n\n this.modals.register(chatGuid, modal.title, {\n viewId,\n callbackId: modal.callbackId,\n selectId: select.id,\n options: select.options,\n contextId,\n privateMetadata: modal.privateMetadata,\n });\n\n return { viewId };\n }\n\n renderFormatted(content: FormattedContent): string {\n return this.formatConverter.fromAst(content);\n }\n\n encodeThreadId(platformData: iMessageThreadId): string {\n return encodeThreadId(platformData);\n }\n\n decodeThreadId(threadId: string): iMessageThreadId {\n return decodeThreadId(threadId);\n }\n\n isDM(threadId: string): boolean {\n return isDMChatGuid(decodeThreadId(threadId).chatGuid);\n }\n\n async startGatewayListener(\n options: WebhookOptions,\n durationMs = 180_000,\n abortSignal?: AbortSignal\n ): Promise<Response> {\n if (!this.chat) {\n return new Response(\"Chat instance not initialized\", { status: 500 });\n }\n if (!options.waitUntil) {\n return new Response(\"waitUntil not provided\", { status: 500 });\n }\n if (!(this.app && this.pump)) {\n return new Response(\"Adapter not initialized\", { status: 500 });\n }\n\n this.logger.info(\"Starting iMessage Gateway listener\", {\n durationMs,\n mode: this.local ? \"local\" : \"remote\",\n });\n\n this.gatewayOptions = options;\n this.pump.ensureRunning();\n\n const listenerPromise = new Promise<void>((resolve) => {\n const timeout = setTimeout(resolve, durationMs);\n if (abortSignal) {\n const onAbort = () => {\n this.logger.info(\"iMessage Gateway listener received abort signal\");\n clearTimeout(timeout);\n this.pump?.stop();\n resolve();\n };\n if (abortSignal.aborted) {\n onAbort();\n return;\n }\n abortSignal.addEventListener(\"abort\", onAbort, { once: true });\n }\n });\n options.waitUntil(listenerPromise);\n\n return new Response(\n JSON.stringify({\n status: \"listening\",\n durationMs,\n mode: this.local ? \"local\" : \"remote\",\n message: `Gateway listener started, will run for ${durationMs / 1000} seconds`,\n }),\n {\n status: 200,\n headers: { \"Content-Type\": \"application/json\" },\n }\n );\n }\n\n private routeWebhookMessage(\n payload: SpectrumWebhookPayload,\n options?: WebhookOptions\n ): void {\n if (!this.chat) {\n return;\n }\n\n const { message, space } = payload;\n // Parity with the gateway path: surface only inbound text/attachment\n // messages — skip the bot's own echoes and inbound reactions.\n if (message.direction === \"outbound\") {\n return;\n }\n if (message.content?.type === \"reaction\") {\n return;\n }\n\n const chatMessage = buildChatMessageFromWebhook(message, space);\n this.chat.processMessage(this, chatMessage.threadId, chatMessage, options);\n }\n\n private async routeInbound(\n space: SpectrumSpace,\n message: SpectrumMessage,\n options?: WebhookOptions\n ): Promise<void> {\n if (!this.chat) {\n return;\n }\n\n this.cache.remember(space, message);\n\n const contentType = message.content.type;\n if (contentType === \"poll_option\") {\n this.handlePollOption(space, message, options);\n return;\n }\n if (contentType === \"reaction\") {\n // Inbound reactions are not surfaced to Chat SDK (parity with the\n // previous adapter, which only forwarded text/attachment messages).\n return;\n }\n if (message.direction === \"outbound\") {\n return;\n }\n\n const chatMessage = buildChatMessage(message, space);\n this.chat.processMessage(this, chatMessage.threadId, chatMessage, options);\n }\n\n private handlePollOption(\n space: SpectrumSpace,\n message: SpectrumMessage,\n options?: WebhookOptions\n ): void {\n if (!this.chat) {\n return;\n }\n const content = message.content;\n if (content.type !== \"poll_option\") {\n return;\n }\n // Only count a cast vote, not a deselection.\n if (!content.selected) {\n return;\n }\n\n const resolved = this.modals.resolveVote(\n space.id,\n content.poll.title,\n content.option.title\n );\n if (!resolved) {\n this.logger.debug(\"Poll vote did not match a known modal, skipping\", {\n title: content.poll.title,\n option: content.option.title,\n });\n return;\n }\n\n const { meta, value } = resolved;\n const handle = message.sender?.id ?? \"\";\n\n this.chat.processModalSubmit(\n {\n adapter: this,\n callbackId: meta.callbackId,\n privateMetadata: meta.privateMetadata,\n viewId: meta.viewId,\n user: {\n userId: handle,\n userName: handle,\n fullName: handle,\n isBot: false,\n isMe: false,\n },\n values: { [meta.selectId]: value },\n raw: message,\n },\n meta.contextId,\n options\n );\n }\n\n /**\n * Resolve a sendable spectrum-ts `Space` for a thread.\n *\n * Prefers a live `Space` cached from the inbound stream (correct sending\n * line, no extra round-trip). On a miss — e.g. a webhook-only deployment, or\n * a cold send — it rebuilds the Space from the chat GUID via\n * `imessage(app).space.get(chatGuid)`, which works for DMs and groups alike.\n * The rebuild can still fail when several iMessage lines are configured and\n * spectrum-ts cannot infer which line the chat belongs to (`space.get`\n * requires `params.phone` there). Returns `undefined` when no Space can be\n * obtained.\n */\n private async resolveSpace(\n threadId: string\n ): Promise<SpectrumSpace | undefined> {\n const { chatGuid } = decodeThreadId(threadId);\n const cached = this.cache.getSpace(chatGuid);\n if (cached) {\n return cached;\n }\n if (!this.app) {\n return;\n }\n // `HasProvider` over the default provider tuple won't narrow to `true`, so\n // the call types as `never`; cast to the slice of the instance we use.\n const platform = imessage(this.app) as unknown as {\n space: { get(id: string): Promise<SpectrumSpace> };\n };\n try {\n const space = await platform.space.get(chatGuid);\n this.cache.rememberSpace(space);\n return space;\n } catch (error) {\n this.logger.debug(\"Could not rebuild Space from chat GUID\", {\n chatGuid,\n error: String(error),\n });\n return;\n }\n }\n\n private async requireSpace(\n threadId: string,\n action: string\n ): Promise<SpectrumSpace> {\n const space = await this.resolveSpace(threadId);\n if (!space) {\n throw new NotImplementedError(\n `${action} could not resolve this thread. With multiple iMessage ` +\n \"lines configured, spectrum-ts needs the chat's sending line to \" +\n \"rebuild an unseen thread. Respond within a received message's \" +\n \"thread instead.\",\n action\n );\n }\n return space;\n }\n\n private async resolveMessage(\n threadId: string,\n messageId: string\n ): Promise<SpectrumMessage | undefined> {\n const cached = this.cache.getMessage(messageId);\n if (cached) {\n return cached;\n }\n const space = await this.resolveSpace(threadId);\n if (!space) {\n return;\n }\n return (await space.getMessage(messageId)) ?? undefined;\n }\n}\n\nfunction toClientArray(\n clients: IMessageClientEntry | IMessageClientEntry[] | undefined\n): IMessageClientEntry[] | undefined {\n if (!clients) {\n return;\n }\n return Array.isArray(clients) ? clients : [clients];\n}\n","import { ValidationError } from \"@chat-adapter/shared\";\nimport type { Logger } from \"chat\";\nimport type { IMessageClientEntry } from \"./types\";\n\nexport const SHARED_PHONE = \"shared\";\n\n/** Provider config shape accepted by `imessage.config(...)`. */\nexport type IMessageProviderConfig =\n | { local: true }\n | { clients?: IMessageClientEntry[]; local?: false };\n\nexport interface iMessageAdapterLocalConfig {\n /** Unused in local mode; accepted for symmetry/back-compat. */\n apiKey?: string;\n local: true;\n logger: Logger;\n /** Unused in local mode; accepted for symmetry/back-compat. */\n serverUrl?: string;\n}\n\nexport interface iMessageAdapterRemoteConfig {\n /** Legacy self-host token. Mapped to a `clients` entry's `token`. */\n apiKey?: string;\n /** Explicit self-host gRPC clients (advanced). */\n clients?: IMessageClientEntry | IMessageClientEntry[];\n local: false;\n logger: Logger;\n /** Routing/identity phone for legacy self-host (defaults to `\"shared\"`). */\n phone?: string;\n /** Spectrum Cloud project id (recommended remote path). */\n projectId?: string;\n /** Spectrum Cloud project secret (recommended remote path). */\n projectSecret?: string;\n /** Legacy self-host endpoint. Now a gRPC `host:port` (see README). */\n serverUrl?: string;\n /** Per-webhook signing secret for verifying Spectrum Cloud deliveries. */\n webhookSecret?: string;\n}\n\nexport type iMessageAdapterConfig =\n | iMessageAdapterLocalConfig\n | iMessageAdapterRemoteConfig;\n\nexport interface CreateiMessageAdapterOptions {\n apiKey?: string;\n clients?: IMessageClientEntry | IMessageClientEntry[];\n local?: boolean;\n logger?: Logger;\n phone?: string;\n projectId?: string;\n projectSecret?: string;\n serverUrl?: string;\n webhookSecret?: string;\n}\n\nconst URL_SCHEME_RE = /^[a-z][a-z0-9+.-]*:\\/\\//i;\n\n/**\n * Normalize a legacy `serverUrl` into a gRPC `host:port` address.\n *\n * `@photon-ai/advanced-imessage` (the transport spectrum-ts uses) speaks gRPC,\n * not HTTP/Socket.IO, so any scheme is stripped and a default `:443` port is\n * appended when none is present.\n */\nexport function deriveAddress(serverUrl: string): string {\n const trimmed = serverUrl.trim();\n const hasScheme = URL_SCHEME_RE.test(trimmed);\n // Parse via URL so host/port (including bracketed IPv6) are handled\n // correctly. `URL.hostname` already wraps IPv6 in brackets — don't re-wrap.\n const url = new URL(hasScheme ? trimmed : `https://${trimmed}`);\n return `${url.hostname}:${url.port || \"443\"}`;\n}\n\n/** The resolved remote-auth fields the adapter holds. */\nexport interface RemoteAuth {\n apiKey?: string;\n clients?: IMessageClientEntry[];\n phone?: string;\n projectId?: string;\n projectSecret?: string;\n serverUrl?: string;\n}\n\n/**\n * Translate the adapter's stored config into the `imessage.config(...)` payload\n * plus any Spectrum Cloud credentials.\n */\nexport function resolveSpectrumConfig(\n local: boolean,\n auth: RemoteAuth\n): {\n projectId?: string;\n projectSecret?: string;\n providerConfig: IMessageProviderConfig;\n} {\n if (local) {\n return { providerConfig: { local: true } };\n }\n if (auth.projectId && auth.projectSecret) {\n return {\n providerConfig: {},\n projectId: auth.projectId,\n projectSecret: auth.projectSecret,\n };\n }\n if (auth.clients?.length) {\n return { providerConfig: { clients: auth.clients } };\n }\n if (auth.serverUrl && auth.apiKey) {\n return {\n providerConfig: {\n clients: [\n {\n address: deriveAddress(auth.serverUrl),\n token: auth.apiKey,\n phone: auth.phone ?? SHARED_PHONE,\n },\n ],\n },\n };\n }\n throw new ValidationError(\n \"imessage\",\n \"Remote mode requires Spectrum Cloud credentials (projectId + projectSecret), explicit clients, or serverUrl + apiKey.\"\n );\n}\n","import type {\n Message as SpectrumMessage,\n Space as SpectrumSpace,\n} from \"spectrum-ts\";\n\nconst DEFAULT_MAX_SPACES = 256;\nconst DEFAULT_MAX_MESSAGES = 1024;\n\n/**\n * Bounded, insertion-order (FIFO-evicted) cache of the Spaces and Messages seen\n * on the inbound stream. The stateless, threadId-addressed Adapter methods\n * (`postMessage`, `addReaction`, …) resolve their target Space/Message from\n * here — spectrum-ts can construct neither from a bare id.\n */\nexport class InboundCache {\n private readonly spaces = new Map<string, SpectrumSpace>();\n private readonly messages = new Map<string, SpectrumMessage>();\n private readonly maxSpaces: number;\n private readonly maxMessages: number;\n\n constructor(\n maxSpaces = DEFAULT_MAX_SPACES,\n maxMessages = DEFAULT_MAX_MESSAGES\n ) {\n this.maxSpaces = maxSpaces;\n this.maxMessages = maxMessages;\n }\n\n /** Cache the Space + Message (and any group sub-items) from one inbound event. */\n remember(space: SpectrumSpace, message: SpectrumMessage): void {\n this.rememberSpace(space);\n this.rememberMessage(message);\n if (message.content.type === \"group\") {\n for (const item of message.content.items) {\n this.rememberMessage(item);\n }\n }\n }\n\n rememberSpace(space: SpectrumSpace): void {\n this.spaces.set(space.id, space);\n evict(this.spaces, this.maxSpaces);\n }\n\n rememberMessage(message: SpectrumMessage): void {\n this.messages.set(message.id, message);\n evict(this.messages, this.maxMessages);\n }\n\n getSpace(chatGuid: string): SpectrumSpace | undefined {\n return this.spaces.get(chatGuid);\n }\n\n getMessage(id: string): SpectrumMessage | undefined {\n return this.messages.get(id);\n }\n}\n\nfunction evict(map: Map<string, unknown>, max: number): void {\n if (map.size <= max) {\n return;\n }\n const overflow = map.size - max;\n let removed = 0;\n for (const key of map.keys()) {\n if (removed >= overflow) {\n break;\n }\n map.delete(key);\n removed += 1;\n }\n}\n","import type { Logger } from \"chat\";\nimport type {\n Message as SpectrumMessage,\n Space as SpectrumSpace,\n} from \"spectrum-ts\";\n\ntype InboundTuple = [SpectrumSpace, SpectrumMessage];\n\ntype OnMessage = (\n space: SpectrumSpace,\n message: SpectrumMessage\n) => Promise<void>;\n\n/**\n * A single, long-lived consumer of spectrum-ts's `app.messages` stream. One\n * persistent pump (rather than a fresh subscription per gateway call) avoids\n * dropping an in-flight message on timeout and keeps the connection warm across\n * overlapping cron windows.\n */\nexport class MessagePump {\n private started = false;\n private iterator: AsyncIterator<InboundTuple> | null = null;\n private readonly source: () => AsyncIterable<InboundTuple>;\n private readonly onMessage: OnMessage;\n private readonly logger: Logger;\n\n constructor(\n source: () => AsyncIterable<InboundTuple>,\n onMessage: OnMessage,\n logger: Logger\n ) {\n this.source = source;\n this.onMessage = onMessage;\n this.logger = logger;\n }\n\n /** Start consuming if not already running. Idempotent. */\n ensureRunning(): void {\n if (this.started) {\n return;\n }\n this.started = true;\n\n const iterator = this.source()[Symbol.asyncIterator]();\n this.iterator = iterator;\n\n this.consume(iterator).catch(() => {\n // consume() handles its own errors; this is an unreachable safety net.\n });\n }\n\n /** Close the underlying stream and stop consuming. */\n stop(): void {\n const iterator = this.iterator;\n this.iterator = null;\n this.started = false;\n iterator?.return?.().catch(() => {\n // ignore teardown errors\n });\n }\n\n private async consume(iterator: AsyncIterator<InboundTuple>): Promise<void> {\n try {\n while (true) {\n const next = await iterator.next();\n if (next.done) {\n break;\n }\n const [space, message] = next.value;\n try {\n await this.onMessage(space, message);\n } catch (error) {\n this.logger.error(\"iMessage inbound handler error\", {\n error: String(error),\n });\n }\n }\n } catch (error) {\n this.logger.error(\"iMessage message stream error\", {\n error: String(error),\n });\n } finally {\n // Reset so a future ensureRunning() can restart the pump if the stream\n // ended/threw on its own. Guard against clobbering a newer iterator\n // installed by a concurrent restart.\n if (this.iterator === iterator) {\n this.iterator = null;\n this.started = false;\n }\n this.logger.info(\"iMessage Gateway listener stopped\");\n }\n }\n}\n","import { Message, parseMarkdown } from \"chat\";\nimport type {\n Content as SpectrumContent,\n Message as SpectrumMessage,\n Space as SpectrumSpace,\n} from \"spectrum-ts\";\nimport { encodeThreadId, isDMChatGuid } from \"./thread\";\n\n/**\n * The provider-agnostic fields a Chat SDK `Message` is built from. Both the\n * gateway (live spectrum-ts `Message`) and the webhook (plain JSON delivery)\n * paths normalize down to this shape.\n */\nexport interface InboundMessageFields {\n chatGuid: string;\n content: SpectrumContent;\n id: string;\n isOutbound: boolean;\n raw: unknown;\n senderId: string;\n timestamp: Date;\n}\n\n/** Build the Chat SDK `Message` from already-normalized inbound fields. */\nexport function buildChatMessageFromFields(\n fields: InboundMessageFields\n): Message {\n const text = extractText(fields.content);\n\n return new Message({\n id: fields.id,\n threadId: encodeThreadId({ chatGuid: fields.chatGuid }),\n text,\n formatted: parseMarkdown(text),\n author: {\n userId: fields.senderId,\n userName: fields.senderId,\n fullName: fields.senderId,\n isBot: false,\n isMe: fields.isOutbound,\n },\n metadata: { dateSent: fields.timestamp, edited: false },\n attachments: extractAttachments(fields.content).map((a) => ({\n type: getAttachmentType(a.mimeType),\n name: a.name,\n mimeType: a.mimeType,\n size: a.size ?? 0,\n })),\n raw: fields.raw,\n isMention: isDMChatGuid(fields.chatGuid),\n });\n}\n\n/** Build the Chat SDK `Message` the adapter surfaces from a spectrum-ts Message. */\nexport function buildChatMessage(\n message: SpectrumMessage,\n space: SpectrumSpace\n): Message {\n return buildChatMessageFromFields({\n id: message.id,\n chatGuid: space.id,\n content: message.content,\n senderId: message.sender?.id ?? \"\",\n isOutbound: message.direction === \"outbound\",\n timestamp: message.timestamp,\n raw: message,\n });\n}\n\nfunction extractText(content: SpectrumContent): string {\n switch (content.type) {\n case \"text\":\n return content.text;\n case \"richlink\":\n return String(content.url);\n case \"poll\":\n return content.title;\n case \"group\":\n return content.items\n .map((item) => extractText(item.content))\n .filter((t) => t.length > 0)\n .join(\"\\n\");\n default:\n return \"\";\n }\n}\n\ninterface ExtractedAttachment {\n mimeType: string;\n name: string;\n size?: number;\n}\n\nfunction extractAttachments(content: SpectrumContent): ExtractedAttachment[] {\n const out: ExtractedAttachment[] = [];\n const visit = (c: SpectrumContent): void => {\n if (c.type === \"attachment\") {\n out.push({ name: c.name, mimeType: c.mimeType, size: c.size });\n } else if (c.type === \"voice\") {\n const voice = c as { mimeType: string; name?: string; size?: number };\n out.push({\n name: voice.name ?? \"voice\",\n mimeType: voice.mimeType,\n size: voice.size,\n });\n } else if (c.type === \"group\") {\n for (const item of c.items) {\n visit(item.content);\n }\n }\n };\n visit(content);\n return out;\n}\n\nfunction getAttachmentType(\n mimeType?: string\n): \"image\" | \"video\" | \"audio\" | \"file\" {\n if (!mimeType) {\n return \"file\";\n }\n if (mimeType.startsWith(\"image/\")) {\n return \"image\";\n }\n if (mimeType.startsWith(\"video/\")) {\n return \"video\";\n }\n if (mimeType.startsWith(\"audio/\")) {\n return \"audio\";\n }\n return \"file\";\n}\n","import { ValidationError } from \"@chat-adapter/shared\";\nimport type { iMessageThreadId } from \"../types\";\n\nconst THREAD_PREFIX = \"imessage:\";\n\nexport function encodeThreadId(platformData: iMessageThreadId): string {\n return `${THREAD_PREFIX}${platformData.chatGuid}`;\n}\n\nexport function decodeThreadId(threadId: string): iMessageThreadId {\n if (!threadId.startsWith(THREAD_PREFIX)) {\n throw new ValidationError(\n \"imessage\",\n `Invalid iMessage thread ID: ${threadId}`\n );\n }\n const chatGuid = threadId.slice(THREAD_PREFIX.length);\n if (!chatGuid) {\n throw new ValidationError(\n \"imessage\",\n `Invalid iMessage thread ID: ${threadId} (empty chat GUID)`\n );\n }\n return { chatGuid };\n}\n\n/** DM chat GUIDs use the `;-;` separator; group chats use `;+;`. */\nexport function isDMChatGuid(chatGuid: string): boolean {\n return chatGuid.includes(\";-;\");\n}\n","import type { ModalPollMeta } from \"../types\";\n\nconst DEFAULT_MAX_MODALS = 512;\n\nexport interface ResolvedVote {\n meta: ModalPollMeta;\n value: string;\n}\n\n/**\n * Tracks Chat SDK modals that were rendered as iMessage native polls and maps\n * inbound votes back to them.\n *\n * iMessage `poll_option` votes carry no poll GUID — only the poll title and the\n * chosen option's title — so votes are matched by `${chatGuid}::${pollTitle}`\n * and the Chat SDK option `value` is recovered from the stored option list.\n *\n * Entries are retained (not deleted on the first vote) so multi-participant\n * polls keep resolving, and bounded with FIFO eviction so the map cannot grow\n * without bound over the adapter's lifetime.\n */\nexport class ModalPollRegistry {\n private readonly byTitle = new Map<string, ModalPollMeta>();\n private readonly maxEntries: number;\n\n constructor(maxEntries = DEFAULT_MAX_MODALS) {\n this.maxEntries = maxEntries;\n }\n\n register(chatGuid: string, title: string, meta: ModalPollMeta): void {\n const key = titleKey(chatGuid, title);\n // Re-registering moves the key to the most-recent position.\n this.byTitle.delete(key);\n this.byTitle.set(key, meta);\n if (this.byTitle.size > this.maxEntries) {\n const oldest = this.byTitle.keys().next().value;\n if (oldest !== undefined) {\n this.byTitle.delete(oldest);\n }\n }\n }\n\n resolveVote(\n chatGuid: string,\n pollTitle: string,\n optionTitle: string\n ): ResolvedVote | undefined {\n const meta = this.byTitle.get(titleKey(chatGuid, pollTitle));\n if (!meta) {\n return;\n }\n const option = meta.options.find((o) => o.label === optionTitle);\n if (!option) {\n // Unknown option label — don't fabricate an unregistered submit value.\n return;\n }\n return { meta, value: option.value };\n }\n}\n\nfunction titleKey(chatGuid: string, title: string): string {\n return `${chatGuid}::${title}`;\n}\n","import { ValidationError } from \"@chat-adapter/shared\";\nimport type { EmojiValue, FileUpload } from \"chat\";\nimport { lookup as lookupMimeType } from \"mime-types\";\nimport { attachment, type ContentBuilder } from \"spectrum-ts\";\n\nconst EMOJI_GLYPHS: Record<string, string> = {\n heart: \"❤️\",\n love: \"❤️\",\n thumbs_up: \"👍\",\n like: \"👍\",\n thumbs_down: \"👎\",\n dislike: \"👎\",\n laugh: \"😂\",\n emphasize: \"‼️\",\n exclamation: \"‼️\",\n question: \"❓\",\n};\n\n/**\n * Map a Chat SDK emoji (name or `EmojiValue`) to the glyph spectrum-ts maps to\n * an iMessage tapback. Throws on an unsupported emoji.\n */\nexport function emojiToGlyph(emoji: EmojiValue | string): string {\n const name = typeof emoji === \"string\" ? emoji : emoji.name;\n const glyph = EMOJI_GLYPHS[name];\n if (!glyph) {\n throw new ValidationError(\n \"imessage\",\n `Unsupported iMessage tapback: \"${name}\". Supported: heart, thumbs_up, thumbs_down, laugh, emphasize, question`\n );\n }\n return glyph;\n}\n\n/** Convert a Chat SDK file upload into a spectrum-ts attachment content builder. */\nexport async function fileToAttachment(\n file: FileUpload\n): Promise<ContentBuilder> {\n const data = file.data;\n let buffer: Buffer;\n if (Buffer.isBuffer(data)) {\n buffer = data;\n } else if (data instanceof Blob) {\n buffer = Buffer.from(await data.arrayBuffer());\n } else {\n buffer = Buffer.from(data as ArrayBuffer);\n }\n\n const name = file.filename || \"attachment\";\n // Always resolve an explicit MIME type: spectrum's attachment() throws if it\n // can't infer one from `name` (no octet-stream fallback, and dotted-but-\n // unknown extensions still miss). Use the upload's type, else infer from the\n // extension, else a generic binary type.\n const mimeType =\n file.mimeType || lookupMimeType(name) || \"application/octet-stream\";\n\n return attachment(buffer, { name, mimeType });\n}\n","import { createHmac, timingSafeEqual } from \"node:crypto\";\nimport type { Message } from \"chat\";\nimport type { Content as SpectrumContent } from \"spectrum-ts\";\nimport { buildChatMessageFromFields } from \"./inbound\";\n\n/**\n * Spectrum Cloud webhook delivery. Spectrum's service POSTs message events to a\n * registered URL as JSON, signed with a per-webhook secret.\n *\n * See https://photon.codes/docs/webhooks/overview\n */\n\n/** Lowercase header names — `Headers.get` is case-insensitive per the spec. */\nexport const SPECTRUM_SIGNATURE_HEADER = \"x-spectrum-signature\";\nexport const SPECTRUM_TIMESTAMP_HEADER = \"x-spectrum-timestamp\";\nexport const SPECTRUM_EVENT_HEADER = \"x-spectrum-event\";\n\n/** The only event Spectrum Cloud delivers today. */\nexport const SPECTRUM_MESSAGES_EVENT = \"messages\";\n\n/** Reject deliveries whose timestamp drifts more than this from now (replay guard). */\nconst TIMESTAMP_TOLERANCE_SEC = 5 * 60;\n\nexport interface SpectrumWebhookSpace {\n id: string;\n phone?: string;\n platform?: string;\n type?: \"dm\" | \"group\";\n}\n\nexport interface SpectrumWebhookMessage {\n content: { type?: string; [key: string]: unknown };\n direction?: \"inbound\" | \"outbound\";\n id: string;\n platform?: string;\n sender?: { id: string; platform?: string };\n space?: SpectrumWebhookSpace;\n timestamp?: string;\n}\n\nexport interface SpectrumWebhookPayload {\n event: string;\n message: SpectrumWebhookMessage;\n space: SpectrumWebhookSpace;\n}\n\nexport type SignatureVerification =\n | { ok: true }\n | { ok: false; reason: string; status: number };\n\n/**\n * Verify a Spectrum Cloud webhook's HMAC-SHA256 signature.\n *\n * The signing base is `v0:{timestamp}:{rawBody}` and the header value is\n * `v0=<lowercase hex>`. `rawBody` MUST be the exact bytes received — verify\n * before parsing JSON.\n */\nexport function verifySpectrumSignature(opts: {\n now?: number;\n rawBody: string;\n secret: string;\n signature: string | null;\n timestamp: string | null;\n}): SignatureVerification {\n const { rawBody, secret, signature, timestamp } = opts;\n if (!(signature && timestamp)) {\n return { ok: false, status: 400, reason: \"missing signature headers\" };\n }\n\n const nowSec = opts.now ?? Math.floor(Date.now() / 1000);\n const age = Math.abs(nowSec - Number(timestamp));\n if (!Number.isFinite(age) || age > TIMESTAMP_TOLERANCE_SEC) {\n return { ok: false, status: 400, reason: \"stale or invalid timestamp\" };\n }\n\n const expected = `v0=${createHmac(\"sha256\", secret)\n .update(`v0:${timestamp}:${rawBody}`)\n .digest(\"hex\")}`;\n\n // Constant-time compare; lengths must match first or timingSafeEqual throws.\n const a = Buffer.from(expected);\n const b = Buffer.from(signature);\n if (a.length !== b.length || !timingSafeEqual(a, b)) {\n return { ok: false, status: 401, reason: \"bad signature\" };\n }\n\n return { ok: true };\n}\n\n/**\n * Build the Chat SDK `Message` from a Spectrum Cloud webhook delivery.\n *\n * The wire format is the spectrum-ts `Content` union with methods stripped, so\n * the message `content` is structurally compatible with the inbound content\n * extractors (which only read metadata fields).\n */\nexport function buildChatMessageFromWebhook(\n message: SpectrumWebhookMessage,\n space: SpectrumWebhookSpace\n): Message {\n return buildChatMessageFromFields({\n id: message.id,\n chatGuid: space.id,\n content: message.content as unknown as SpectrumContent,\n senderId: message.sender?.id ?? \"\",\n isOutbound: message.direction === \"outbound\",\n timestamp: message.timestamp ? new Date(message.timestamp) : new Date(),\n raw: message,\n });\n}\n","/**\n * iMessage format conversion using AST-based parsing.\n *\n * iMessage supports plain text only -- no rich formatting syntax.\n * The converter strips formatting markers and outputs clean plain text,\n * preserving structure (lists, blockquotes, code blocks) with whitespace.\n */\n\nimport {\n BaseFormatConverter,\n type Content,\n getNodeChildren,\n getNodeValue,\n isBlockquoteNode,\n isCodeNode,\n isDeleteNode,\n isEmphasisNode,\n isInlineCodeNode,\n isLinkNode,\n isListItemNode,\n isListNode,\n isParagraphNode,\n isStrongNode,\n isTextNode,\n parseMarkdown,\n type Root,\n} from \"chat\";\n\nexport class iMessageFormatConverter extends BaseFormatConverter {\n /**\n * Render an AST to iMessage plain text format.\n * Strips all formatting markers since iMessage doesn't support rich text via API.\n */\n fromAst(ast: Root): string {\n return this.fromAstWithNodeConverter(ast, (node) =>\n this.nodeToPlainText(node)\n );\n }\n\n /**\n * Parse iMessage text into an AST.\n * iMessage sends plain text, so we just parse it as markdown.\n */\n toAst(text: string): Root {\n return parseMarkdown(text);\n }\n\n private nodeToPlainText(node: Content): string {\n if (isParagraphNode(node)) {\n return getNodeChildren(node)\n .map((child) => this.nodeToPlainText(child))\n .join(\"\");\n }\n\n if (isTextNode(node)) {\n return node.value;\n }\n\n if (isStrongNode(node) || isEmphasisNode(node) || isDeleteNode(node)) {\n return getNodeChildren(node)\n .map((child) => this.nodeToPlainText(child))\n .join(\"\");\n }\n\n if (isInlineCodeNode(node)) {\n return node.value;\n }\n\n if (isCodeNode(node)) {\n return node.value;\n }\n\n if (isLinkNode(node)) {\n const linkText = getNodeChildren(node)\n .map((child) => this.nodeToPlainText(child))\n .join(\"\");\n return linkText ? `${linkText} (${node.url})` : node.url;\n }\n\n if (isBlockquoteNode(node)) {\n return getNodeChildren(node)\n .map((child) => `> ${this.nodeToPlainText(child)}`)\n .join(\"\\n\");\n }\n\n if (isListNode(node)) {\n return getNodeChildren(node)\n .map((item, i) => {\n const prefix = node.ordered ? `${i + 1}.` : \"-\";\n const content = getNodeChildren(item)\n .map((child) => this.nodeToPlainText(child))\n .join(\"\");\n return `${prefix} ${content}`;\n })\n .join(\"\\n\");\n }\n\n if (isListItemNode(node)) {\n return getNodeChildren(node)\n .map((child) => this.nodeToPlainText(child))\n .join(\"\");\n }\n\n if (node.type === \"break\") {\n return \"\\n\";\n }\n\n if (node.type === \"thematicBreak\") {\n return \"---\";\n }\n\n const children = getNodeChildren(node);\n if (children.length > 0) {\n return children.map((child) => this.nodeToPlainText(child)).join(\"\");\n }\n return getNodeValue(node);\n }\n}\n","import { ValidationError } from \"@chat-adapter/shared\";\nimport { ConsoleLogger } from \"chat\";\nimport { iMessageAdapter } from \"./adapter\";\nimport type { CreateiMessageAdapterOptions } from \"./config\";\n\n/**\n * Decide local vs remote: an explicit local signal (`config.local`, then\n * `IMESSAGE_LOCAL`) wins; otherwise pick remote when remote credentials are\n * present, else local.\n */\nfunction resolveLocalMode(\n explicit: boolean | undefined,\n hasRemoteCreds: boolean\n): boolean {\n if (explicit !== undefined) {\n return explicit;\n }\n const env = process.env.IMESSAGE_LOCAL;\n if (env === \"false\") {\n return false;\n }\n if (env === undefined) {\n return !hasRemoteCreds;\n }\n return true;\n}\n\nfunction assertRemoteCredentials(creds: {\n hasApiKey: boolean;\n hasClients: boolean;\n hasCloud: boolean;\n hasServerUrl: boolean;\n}): void {\n if (creds.hasCloud || creds.hasClients) {\n return;\n }\n if (!creds.hasServerUrl) {\n throw new ValidationError(\n \"imessage\",\n \"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.\"\n );\n }\n if (!creds.hasApiKey) {\n throw new ValidationError(\n \"imessage\",\n \"apiKey is required when local is false. Set IMESSAGE_API_KEY or provide it in config.\"\n );\n }\n}\n\n/**\n * Construct an {@link iMessageAdapter}, filling unset options from environment\n * variables.\n *\n * Mode is chosen by an explicit local signal first — `config.local`, then\n * `IMESSAGE_LOCAL` — otherwise remote when remote credentials are present (cloud\n * `projectId` + `projectSecret`, or self-host `clients` / `serverUrl` +\n * `apiKey`), else local.\n */\nexport function createiMessageAdapter(\n config?: CreateiMessageAdapterOptions\n): iMessageAdapter {\n const logger = config?.logger ?? new ConsoleLogger(\"info\").child(\"imessage\");\n\n const projectId = config?.projectId ?? process.env.IMESSAGE_PROJECT_ID;\n const projectSecret =\n config?.projectSecret ?? process.env.IMESSAGE_PROJECT_SECRET;\n const clients = config?.clients;\n // Normalize once: trim so surrounding whitespace neither passes validation\n // nor reaches the adapter (where it would break the gRPC address/token).\n const serverUrl = (\n config?.serverUrl ?? process.env.IMESSAGE_SERVER_URL\n )?.trim();\n const apiKey = (config?.apiKey ?? process.env.IMESSAGE_API_KEY)?.trim();\n const phone = config?.phone ?? process.env.IMESSAGE_PHONE;\n const webhookSecret = (\n config?.webhookSecret ?? process.env.IMESSAGE_WEBHOOK_SECRET\n )?.trim();\n\n const hasClients = Array.isArray(clients)\n ? clients.length > 0\n : Boolean(clients);\n const hasCloud = Boolean(projectId && projectSecret);\n const hasServerUrl = Boolean(serverUrl);\n const hasApiKey = Boolean(apiKey);\n const hasRemoteCreds = hasCloud || hasClients || (hasServerUrl && hasApiKey);\n\n if (resolveLocalMode(config?.local, hasRemoteCreds)) {\n return new iMessageAdapter({ local: true, logger, serverUrl, apiKey });\n }\n\n assertRemoteCredentials({ hasApiKey, hasClients, hasCloud, hasServerUrl });\n\n return new iMessageAdapter({\n local: false,\n logger,\n projectId,\n projectSecret,\n clients,\n serverUrl,\n apiKey,\n phone,\n webhookSecret,\n });\n}\n"],"mappings":"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","names":["ValidationError","ValidationError","ValidationError","parseMarkdown","ValidationError","ValidationError","ValidationError"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@photon-ai/chat-adapter-imessage",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "iMessage adapter for Chat SDK",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -15,35 +15,13 @@
|
|
|
15
15
|
"files": [
|
|
16
16
|
"dist"
|
|
17
17
|
],
|
|
18
|
-
"scripts": {
|
|
19
|
-
"build": "tsup",
|
|
20
|
-
"dev": "tsup --watch",
|
|
21
|
-
"test": "vitest run --coverage",
|
|
22
|
-
"test:watch": "vitest",
|
|
23
|
-
"typecheck": "tsc --noEmit",
|
|
24
|
-
"check": "ultracite check",
|
|
25
|
-
"fix": "ultracite fix",
|
|
26
|
-
"clean": "rm -rf dist coverage"
|
|
27
|
-
},
|
|
28
18
|
"peerDependencies": {
|
|
29
19
|
"chat": "^4.14.0"
|
|
30
20
|
},
|
|
31
21
|
"dependencies": {
|
|
32
|
-
"@chat-adapter/shared": "^4.
|
|
33
|
-
"mime-types": "^3.0.
|
|
34
|
-
"spectrum-ts": "^
|
|
35
|
-
},
|
|
36
|
-
"devDependencies": {
|
|
37
|
-
"@biomejs/biome": "^2.4.12",
|
|
38
|
-
"@types/mime-types": "^3.0.1",
|
|
39
|
-
"@types/node": "^22.10.2",
|
|
40
|
-
"@vitest/coverage-v8": "^4.0.18",
|
|
41
|
-
"chat": "^4.15.0",
|
|
42
|
-
"clean-publish": "^7.0.0",
|
|
43
|
-
"tsup": "^8.3.5",
|
|
44
|
-
"typescript": "^5.9.3",
|
|
45
|
-
"ultracite": "7.8.1",
|
|
46
|
-
"vitest": "^4.0.18"
|
|
22
|
+
"@chat-adapter/shared": "^4.32.0",
|
|
23
|
+
"mime-types": "^3.0.2",
|
|
24
|
+
"spectrum-ts": "^8.2.1"
|
|
47
25
|
},
|
|
48
26
|
"packageManager": "pnpm@10.30.3",
|
|
49
27
|
"publishConfig": {
|