@photon-ai/chat-adapter-imessage 2.1.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +107 -8
- package/dist/index.d.ts +299 -8
- package/dist/index.js +585 -39
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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 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"]}
|
|
1
|
+
{"version":3,"sources":["../src/adapter.ts","../src/background.ts","../src/config.ts","../src/effects.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/miniapp.ts","../src/voice.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 type AppUrl,\n app as appContent,\n type ContentBuilder,\n markdown as markdownContent,\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 {\n customizedMiniApp,\n effect as effectContent,\n imessage,\n} from \"spectrum-ts/providers/imessage\";\nimport {\n type BackgroundInput,\n type BackgroundOptions,\n resolveBackground,\n} from \"./background\";\nimport { type iMessageAdapterConfig, resolveSpectrumConfig } from \"./config\";\nimport {\n type IMessageMessageEffect,\n type iMessageEffectName,\n resolveEffect,\n} from \"./effects\";\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 { isAppUrl, type MiniAppCard, resolveMiniApp } from \"./miniapp\";\nimport type { IMessageClientEntry, iMessageThreadId } from \"./types\";\nimport { resolveVoice, type VoiceInput, type VoiceOptions } from \"./voice\";\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 /**\n * Build the spectrum content for an outbound message. Markdown-typed inputs\n * are sent via `markdown()` so remote iMessage renders them as native styled\n * text; raw/string/card inputs stay plain `text()`. Returns the rendered\n * `body` too so callers can skip an empty send.\n */\n private toSpectrumContent(message: AdapterPostableMessage): {\n body: string;\n content: ContentBuilder;\n } {\n const { body, markdown } =\n this.formatConverter.renderPostableContent(message);\n return {\n body,\n content: markdown ? markdownContent(body) : textContent(body),\n };\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, content } = this.toSpectrumContent(message);\n const files = extractFiles(message);\n\n let first: SpectrumMessage | undefined;\n if (body && body.trim().length > 0) {\n first = (await space.send(content)) ?? 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 /**\n * Send a message with an iMessage expressive-send effect — a bubble effect\n * (`slam`, `loud`, `gentle`, `invisible`) or a full-screen effect\n * (`confetti`, `fireworks`, `balloons`, `heart`, `lasers`, `celebration`,\n * `sparkles`, `spotlight`, `echo`). Not part of the Chat SDK `Adapter`\n * interface — exposed as an adapter-specific extra (e.g. celebratory confetti\n * on task completion). Remote only.\n *\n * The `effect` argument accepts a friendly name (`\"confetti\"`) or a value from\n * the re-exported `iMessageEffect` map. Effects attach to text content only,\n * so this requires non-empty text.\n */\n async sendEffect(\n threadId: string,\n message: AdapterPostableMessage,\n effect: IMessageMessageEffect | iMessageEffectName\n ): Promise<RawMessage> {\n if (this.local) {\n throw new NotImplementedError(\n \"sendEffect is not supported in local mode\",\n \"sendEffect\"\n );\n }\n\n const space = await this.requireSpace(threadId, \"sendEffect\");\n const { body, content } = this.toSpectrumContent(message);\n if (!body || body.trim().length === 0) {\n throw new ValidationError(\n \"imessage\",\n \"sendEffect requires non-empty text content\"\n );\n }\n\n const effectId = resolveEffect(effect);\n const sent = await space.send(effectContent(content, effectId));\n if (!sent) {\n throw new ValidationError(\n \"imessage\",\n \"sendEffect could not send the message\"\n );\n }\n\n return { id: sent.id, threadId, raw: sent };\n }\n\n /**\n * Send an iMessage mini-app card — an `MSMessageExtension` balloon, the\n * closest iMessage gets to a rich card (à la Slack Block Kit) instead of a\n * bare link. Not part of the Chat SDK `Adapter` interface — exposed as an\n * adapter-specific extra. Remote only.\n *\n * Two forms:\n *\n * - **Just a URL** — pass a string (or a `Promise`/thunk resolving to one, so\n * the link can be minted at send time). This is the lightweight `app(url)`\n * card: the URL is rendered as a mini-app with no extension identifiers\n * required.\n * - **A full {@link MiniAppCard}** — pass an object to control the bubble's\n * image, captions, and the exact iMessage extension that opens on tap. Its\n * `appName`, `teamId`, and `extensionBundleId` identify that extension.\n */\n async sendMiniApp(threadId: string, url: AppUrl): Promise<RawMessage>;\n async sendMiniApp(threadId: string, card: MiniAppCard): Promise<RawMessage>;\n async sendMiniApp(\n threadId: string,\n input: MiniAppCard | AppUrl\n ): Promise<RawMessage> {\n if (this.local) {\n throw new NotImplementedError(\n \"sendMiniApp is not supported in local mode\",\n \"sendMiniApp\"\n );\n }\n\n const space = await this.requireSpace(threadId, \"sendMiniApp\");\n const content = isAppUrl(input)\n ? appContent(input)\n : customizedMiniApp(await resolveMiniApp(input));\n const sent = await space.send(content);\n if (!sent) {\n throw new ValidationError(\n \"imessage\",\n \"sendMiniApp could not send the card\"\n );\n }\n\n return { id: sent.id, threadId, raw: sent };\n }\n\n /**\n * Send a native iMessage voice note — a real, playable waveform bubble (the\n * message renders with `isAudioMessage`), not an audio file dropped in as an\n * attachment. A natural fit for TTS-capable bots that reply with speech. Not\n * part of the Chat SDK `Adapter` interface — exposed as an adapter-specific\n * extra. Remote only.\n *\n * The `input` is either in-memory audio bytes (`Uint8Array` / `Buffer` /\n * `ArrayBuffer`, a `Blob`, or a Chat SDK `FileUpload`) or an `http(s)` URL (a\n * `URL` or a string) that is fetched at send time. Audio bytes need an\n * `audio/*` MIME type — supply `options.mimeType` (e.g. `\"audio/mp4\"`) or an\n * `options.name` with an audio extension when it can't be inferred.\n */\n async sendVoice(\n threadId: string,\n input: VoiceInput,\n options?: VoiceOptions\n ): Promise<RawMessage> {\n if (this.local) {\n throw new NotImplementedError(\n \"sendVoice is not supported in local mode\",\n \"sendVoice\"\n );\n }\n\n const space = await this.requireSpace(threadId, \"sendVoice\");\n const content = await resolveVoice(input, options);\n const sent = await space.send(content);\n if (!sent) {\n throw new ValidationError(\n \"imessage\",\n \"sendVoice could not send the voice message\"\n );\n }\n\n return { id: sent.id, threadId, raw: sent };\n }\n\n /**\n * Set or clear the chat background — the wallpaper behind a conversation, an\n * iMessage-only touch with no analog on the plain-text competitors. Not part\n * of the Chat SDK `Adapter` interface — exposed as an adapter-specific extra.\n * Remote only.\n *\n * The `input` is either the literal `\"clear\"` (to remove the current\n * background), in-memory image bytes (`Uint8Array` / `Buffer` / `ArrayBuffer`,\n * a `Blob`, or a Chat SDK `FileUpload`), or an `http(s)` URL (a `URL` or a\n * string) that is fetched at send time. Image bytes need an `image/*` MIME\n * type — supply `options.mimeType` (e.g. `\"image/jpeg\"`) or an `options.name`\n * with an image extension when it can't be inferred.\n *\n * Fire-and-forget: iMessage acknowledges the control signal without returning\n * a message, so this resolves to `void` rather than a {@link RawMessage}.\n */\n async setBackground(\n threadId: string,\n input: BackgroundInput,\n options?: BackgroundOptions\n ): Promise<void> {\n if (this.local) {\n throw new NotImplementedError(\n \"setBackground is not supported in local mode\",\n \"setBackground\"\n );\n }\n\n const space = await this.requireSpace(threadId, \"setBackground\");\n const content = await resolveBackground(input, options);\n await space.send(content);\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(this.toSpectrumContent(message).content);\n return { id: messageId, threadId, raw: target };\n }\n\n async deleteMessage(threadId: string, messageId: string): Promise<void> {\n if (this.local) {\n throw new NotImplementedError(\n \"deleteMessage is not supported in local mode\",\n \"deleteMessage\"\n );\n }\n\n const target = await this.resolveMessage(threadId, messageId);\n if (!target) {\n throw new NotImplementedError(\n \"deleteMessage requires the target message to have been sent or received in this session\",\n \"deleteMessage\"\n );\n }\n\n await target.unsend();\n }\n\n parseMessage(raw: unknown): Message {\n const message = raw as SpectrumMessage;\n return buildChatMessage(message, message.space);\n }\n\n /**\n * Fetch a single message by id. spectrum-ts can resolve a message by id\n * (from the inbound cache or the provider's by-id lookup) even though it has\n * no paginated history API, so single-message reads work where\n * `fetchMessages` cannot. Returns `null` when the message can't be resolved.\n */\n async fetchMessage(\n threadId: string,\n messageId: string\n ): Promise<Message | null> {\n const target = await this.resolveMessage(threadId, messageId);\n if (!target) {\n return null;\n }\n return this.parseMessage(target);\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 const reaction = await target.react(glyph);\n if (reaction) {\n this.cache.rememberReaction(messageId, glyph, reaction);\n }\n }\n\n async removeReaction(\n _threadId: string,\n messageId: string,\n emoji: EmojiValue | string\n ): Promise<void> {\n if (this.local) {\n throw new NotImplementedError(\n \"removeReaction is not supported in local mode\",\n \"removeReaction\"\n );\n }\n\n // spectrum-ts message ids are globally unique, so the target message id\n // alone keys the reaction handle — the thread id isn't needed here.\n const glyph = emojiToGlyph(emoji);\n const reaction = this.cache.takeReaction(messageId, glyph);\n if (!reaction) {\n throw new NotImplementedError(\n \"removeReaction requires the reaction to have been added via addReaction in this session\",\n \"removeReaction\"\n );\n }\n\n await reaction.unsend();\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 /**\n * Cold-start a DM with a phone number / handle. spectrum-ts resolves (or\n * creates) the 1:1 conversation from the participant via `space.create`, so\n * the bot can message a user it has never received from. Returns the encoded\n * thread id, ready to pass to `postMessage`.\n */\n async openDM(userId: string): Promise<string> {\n if (!this.app) {\n throw new NotImplementedError(\n \"openDM requires the adapter to be initialized\",\n \"openDM\"\n );\n }\n\n const space = await this.platformSpaces().create(userId);\n this.cache.rememberSpace(space);\n return encodeThreadId({ chatGuid: space.id });\n }\n\n /**\n * Mark a received message (and the conversation up to it) as read, surfacing\n * a read receipt where iMessage supports one. Remote only; not part of the\n * Chat SDK `Adapter` interface — exposed as an adapter-specific extra.\n */\n async markRead(threadId: string, messageId: string): Promise<void> {\n if (this.local) {\n throw new NotImplementedError(\n \"markRead is not supported in local mode\",\n \"markRead\"\n );\n }\n\n const target = await this.resolveMessage(threadId, messageId);\n if (!target) {\n throw new NotImplementedError(\n \"markRead requires the target message to have been received in this session\",\n \"markRead\"\n );\n }\n\n await target.read();\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 try {\n const space = await this.platformSpaces().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 /**\n * The iMessage provider's Space namespace (`get` / `create`). `HasProvider`\n * over the default provider tuple won't narrow to `true`, so `imessage(app)`\n * types as `never` — cast to the slice of the instance we use.\n */\n private platformSpaces(): {\n get(id: string): Promise<SpectrumSpace>;\n create(users: string): Promise<SpectrumSpace>;\n } {\n if (!this.app) {\n throw new Error(\"Adapter not initialized\");\n }\n return (\n imessage(this.app) as unknown as {\n space: {\n get(id: string): Promise<SpectrumSpace>;\n create(users: string): Promise<SpectrumSpace>;\n };\n }\n ).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} 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 { FileUpload } from \"chat\";\nimport { lookup as lookupMimeType } from \"mime-types\";\nimport type { ContentBuilder } from \"spectrum-ts\";\nimport { background as backgroundContent } from \"spectrum-ts/providers/imessage\";\n\n/**\n * Image bytes for a chat background. Accepts raw bytes (`Uint8Array` / `Buffer`\n * / `ArrayBuffer`), a `Blob`, or a Chat SDK `FileUpload` — whatever your image\n * pipeline hands back gets normalized to the bytes spectrum-ts expects.\n */\nexport type BackgroundBytes = Uint8Array | ArrayBuffer | Blob | FileUpload;\n\n/**\n * A chat-background source. Either:\n *\n * - the literal `\"clear\"` sentinel, to remove the current background;\n * - in-memory {@link BackgroundBytes};\n * - an `http(s)` URL (a `URL` or a string) that spectrum-ts fetches at send\n * time. Local file paths aren't accepted — read the file into bytes and pass\n * those instead.\n */\nexport type BackgroundInput = \"clear\" | BackgroundBytes | URL | string;\n\n/** Optional chat-background metadata. */\nexport interface BackgroundOptions {\n /**\n * MIME type of the image (`image/*`). Required for raw bytes when the `name`\n * carries no image extension; inferred from the URL / name otherwise.\n */\n mimeType?: string;\n /** File name used to infer the MIME type from its extension. */\n name?: string;\n}\n\nconst IMAGE_MIME_PATTERN = /^image\\//i;\n\n/**\n * Normalize accepted byte inputs to a fresh `Buffer`, alongside any name / MIME\n * hints the input carries. The copy detaches the bytes from any pooled/shared\n * backing buffer (e.g. a Node `Buffer`), matching how attachment, voice, and\n * mini-app bytes are handled — spectrum-ts reads them lazily at send time.\n */\nasync function toBytes(input: BackgroundBytes): Promise<{\n bytes: Buffer;\n mimeHint?: string;\n nameHint?: string;\n}> {\n if (input instanceof Uint8Array) {\n // Buffer is a Uint8Array subclass, so this also covers Node Buffers.\n return { bytes: Buffer.from(input) };\n }\n if (input instanceof ArrayBuffer) {\n return { bytes: Buffer.from(input) };\n }\n if (input instanceof Blob) {\n return {\n bytes: Buffer.from(await input.arrayBuffer()),\n mimeHint: input.type || undefined,\n };\n }\n // Otherwise treat it as a Chat SDK FileUpload and unwrap its `data`.\n const data = (input as FileUpload).data;\n if (data === undefined) {\n throw new ValidationError(\n \"imessage\",\n \"Chat background must be a Uint8Array, ArrayBuffer, Blob, or FileUpload\"\n );\n }\n const nested = await toBytes(data as BackgroundBytes);\n return {\n bytes: nested.bytes,\n mimeHint: (input as FileUpload).mimeType || nested.mimeHint,\n nameHint: (input as FileUpload).filename || nested.nameHint,\n };\n}\n\n/**\n * Resolve a URL source. A `URL` instance passes through; a string is parsed and\n * must be an `http(s)` URL. Returns `undefined` for byte inputs so the caller\n * falls through to the bytes path.\n */\nfunction toBackgroundUrl(input: BackgroundInput): URL | undefined {\n if (input instanceof URL) {\n return input;\n }\n if (typeof input !== \"string\") {\n return;\n }\n let url: URL;\n try {\n url = new URL(input);\n } catch {\n throw new ValidationError(\n \"imessage\",\n `Chat background string input must be an http(s) URL, got \"${input}\". Pass image bytes for local files.`\n );\n }\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") {\n throw new ValidationError(\n \"imessage\",\n `Chat background URL must be http(s), got \"${url.protocol}\"`\n );\n }\n return url;\n}\n\n/**\n * Resolve an `image/*` MIME type for a byte source from an explicit hint (the\n * caller's `mimeType`, or a Blob/FileUpload's own type) or by inference from\n * the name's extension. Throws a `ValidationError` when neither yields an image\n * type — spectrum-ts requires one to set the background from raw bytes.\n */\nfunction resolveBytesMime(\n hint: string | undefined,\n name: string | undefined\n): string {\n if (hint && IMAGE_MIME_PATTERN.test(hint)) {\n return hint;\n }\n if (name) {\n const inferred = lookupMimeType(name);\n if (inferred && IMAGE_MIME_PATTERN.test(inferred)) {\n return inferred;\n }\n }\n throw new ValidationError(\n \"imessage\",\n hint\n ? `Chat background requires an image/* MIME type, got \"${hint}\"`\n : 'Chat background requires an image/* MIME type — pass options.mimeType (e.g. \"image/jpeg\") or a name with an image extension'\n );\n}\n\n/**\n * Validate and normalize a chat-background source into the spectrum-ts\n * `background()` content builder. The `\"clear\"` sentinel removes the current\n * background; URL sources are fetched by spectrum-ts at send time; byte sources\n * are copied to a detached `Buffer` and tagged with a resolved `image/*` MIME\n * type. The builder is a fire-and-forget control signal — remote and\n * iMessage-only.\n */\nexport async function resolveBackground(\n input: BackgroundInput,\n options: BackgroundOptions = {}\n): Promise<ContentBuilder> {\n if (input === \"clear\") {\n return backgroundContent(\"clear\");\n }\n\n if (options.mimeType && !IMAGE_MIME_PATTERN.test(options.mimeType)) {\n throw new ValidationError(\n \"imessage\",\n `Chat background requires an image/* MIME type, got \"${options.mimeType}\"`\n );\n }\n\n const url = toBackgroundUrl(input);\n if (url) {\n // spectrum-ts fetches the URL at send time and infers the MIME type from\n // its path when we don't override it.\n return backgroundContent(\n url,\n options.mimeType ? { mimeType: options.mimeType } : undefined\n );\n }\n\n const { bytes, mimeHint, nameHint } = await toBytes(input as BackgroundBytes);\n const name = options.name ?? nameHint;\n const mimeType = resolveBytesMime(options.mimeType ?? mimeHint, name);\n return backgroundContent(bytes, { mimeType });\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 { ValidationError } from \"@chat-adapter/shared\";\nimport type { IMessageMessageEffect } from \"spectrum-ts/providers/imessage\";\nimport { imessage } from \"spectrum-ts/providers/imessage\";\n\nexport type { IMessageMessageEffect } from \"spectrum-ts/providers/imessage\";\n\n/**\n * iMessage expressive-send effects, keyed by friendly name. Bubble effects\n * (`slam`, `loud`, `gentle`, `invisible`) animate the message bubble; the rest\n * are full-screen effects (`confetti`, `fireworks`, `balloons`, `heart`,\n * `lasers`, `celebration`, `sparkles`, `spotlight`, `echo`).\n *\n * Re-exported from spectrum-ts so callers can reference effects by name without\n * reaching into the provider package — e.g. `iMessageEffect.confetti`.\n */\nexport const iMessageEffect = imessage.effect.message;\n\n/** Accepted effect names (`\"confetti\"`, `\"fireworks\"`, …). */\nexport type iMessageEffectName = keyof typeof iMessageEffect;\n\nconst EFFECT_IDS = new Set<string>(Object.values(iMessageEffect));\nconst EFFECT_NAMES = Object.keys(iMessageEffect);\n\n/**\n * Resolve an effect argument to a spectrum-ts effect id. Accepts either a\n * friendly name (`\"confetti\"`) or the raw effect id\n * (`iMessageEffect.confetti`), so both styles work. Throws a `ValidationError`\n * on an unknown effect.\n */\nexport function resolveEffect(\n effect: IMessageMessageEffect | iMessageEffectName\n): IMessageMessageEffect {\n if (effect in iMessageEffect) {\n return (iMessageEffect as Record<string, IMessageMessageEffect>)[effect];\n }\n if (EFFECT_IDS.has(effect)) {\n return effect as IMessageMessageEffect;\n }\n throw new ValidationError(\n \"imessage\",\n `Unknown iMessage effect: \"${effect}\". Supported: ${EFFECT_NAMES.join(\", \")}`\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 /**\n * Reaction messages this session sent via `addReaction`, keyed by their\n * target so `removeReaction` can `unsend()` them. spectrum-ts models a\n * tapback as its own message, and the only handle to retract one is the\n * `Message` that `react()` returns — there is no by-target removal API.\n */\n private readonly reactions = 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 /** Record the reaction message returned by `react()` for later removal. */\n rememberReaction(\n targetMessageId: string,\n glyph: string,\n reaction: SpectrumMessage\n ): void {\n this.reactions.set(reactionKey(targetMessageId, glyph), reaction);\n evict(this.reactions, this.maxMessages);\n }\n\n /** Look up (and forget) a tracked reaction so it can be `unsend()`-ed once. */\n takeReaction(\n targetMessageId: string,\n glyph: string\n ): SpectrumMessage | undefined {\n const key = reactionKey(targetMessageId, glyph);\n const reaction = this.reactions.get(key);\n if (reaction) {\n this.reactions.delete(key);\n }\n return reaction;\n }\n}\n\nfunction reactionKey(targetMessageId: string, glyph: string): string {\n return `${targetMessageId}::${glyph}`;\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 * Remote iMessage (via spectrum-ts) renders CommonMark natively as styled text\n * (bold/italic/links etc. via UTF-16 formatting ranges), so markdown-typed\n * outbound content is sent through spectrum's `markdown()` builder verbatim --\n * see `renderPostableContent`. The plain-text rendering here (`fromAst`) is\n * still used for inbound parsing, `renderFormatted`, and the raw/card fallback\n * paths: it strips formatting markers and preserves structure (lists,\n * blockquotes, code blocks) with whitespace.\n */\n\nimport {\n type AdapterPostableMessage,\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 stringifyMarkdown,\n} from \"chat\";\n\n/**\n * The spectrum content a postable message should be sent as: the body string\n * plus whether spectrum should render it as native markdown (styled text) or\n * pass it through as plain text.\n */\nexport interface PostableContent {\n body: string;\n markdown: boolean;\n}\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 /**\n * Decide how a postable message should reach spectrum-ts.\n *\n * Markdown-typed inputs -- `{ markdown }` and `{ ast }` -- carry CommonMark\n * the caller wants styled, so their source is preserved verbatim and flagged\n * `markdown: true`; the adapter sends it via spectrum's `markdown()` builder,\n * which renders bold/italic/links/lists as native iMessage styled text.\n *\n * Everything else is pass-through-as-is by contract -- a plain `string` or\n * `{ raw }` must not have stray `*`/`_` reinterpreted as formatting, and\n * cards fall back to plain text -- so those are rendered to plain text and\n * flagged `markdown: false`.\n */\n renderPostableContent(message: AdapterPostableMessage): PostableContent {\n if (message && typeof message === \"object\") {\n if (\"markdown\" in message && typeof message.markdown === \"string\") {\n return { body: message.markdown, markdown: true };\n }\n if (\"ast\" in message && message.ast) {\n return { body: stringifyMarkdown(message.ast), markdown: true };\n }\n }\n return { body: this.renderPostable(message), markdown: false };\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 type { FileUpload } from \"chat\";\nimport type { AppUrl } from \"spectrum-ts\";\nimport type { CustomizedMiniAppInput } from \"spectrum-ts/providers/imessage\";\n\nexport type { AppUrl } from \"spectrum-ts\";\nexport type { CustomizedMiniAppInput } from \"spectrum-ts/providers/imessage\";\n\n/**\n * Image bytes for a mini-app card's inline image. Accepts raw bytes\n * (`Uint8Array` / `Buffer` / `ArrayBuffer`), a `Blob`, or a Chat SDK\n * `FileUpload` — whatever you already have on hand gets converted to the\n * `Uint8Array` spectrum-ts expects.\n */\nexport type MiniAppImage = Uint8Array | ArrayBuffer | Blob | FileUpload;\n\n/**\n * Layout of a mini-app card — everything the recipient sees in the bubble.\n * Every field is optional; supply the ones that make sense for your card. The\n * `image` renders as the card's artwork, with `imageTitle` / `imageSubtitle`\n * overlaid; the caption fields fill the text rows, and `summary` is the\n * fallback text shown where the mini-app can't render.\n */\nexport interface MiniAppCardLayout {\n caption?: string;\n image?: MiniAppImage;\n imageSubtitle?: string;\n imageTitle?: string;\n subcaption?: string;\n summary?: string;\n trailingCaption?: string;\n trailingSubcaption?: string;\n}\n\n/**\n * A native iMessage mini-app card (an `MSMessageExtension` balloon). The\n * `appName`, `teamId`, and `extensionBundleId` identify the iMessage extension\n * that opens when the recipient taps the card, receiving `url`; `appStoreId`\n * optionally points recipients without the extension installed at its App\n * Store entry.\n */\nexport interface MiniAppCard {\n /** Display name of the iMessage extension. */\n appName: string;\n /** Optional App Store id, for recipients without the extension installed. */\n appStoreId?: number;\n /** Bundle id of the `MSMessageExtension` that receives the tap. */\n extensionBundleId: string;\n /** What the recipient sees in the bubble. */\n layout?: MiniAppCardLayout;\n /** Apple Developer team id that signs the extension. */\n teamId: string;\n /** URL handed to the extension when the card is tapped. */\n url: string | URL;\n}\n\n/**\n * Convert accepted image inputs to a fresh, `ArrayBuffer`-backed `Uint8Array`\n * — the exact shape spectrum-ts's schema expects. The copy also detaches the\n * bytes from any pooled/shared backing buffer (e.g. a Node `Buffer`).\n */\nasync function toImageBytes(\n image: MiniAppImage\n): Promise<Uint8Array<ArrayBuffer>> {\n if (image instanceof Uint8Array) {\n // Buffer is a Uint8Array subclass, so this also covers Node Buffers.\n return new Uint8Array(image);\n }\n if (image instanceof ArrayBuffer) {\n return new Uint8Array(image);\n }\n if (image instanceof Blob) {\n return new Uint8Array(await image.arrayBuffer());\n }\n // Otherwise treat it as a Chat SDK FileUpload and unwrap its `data`.\n const data = (image as FileUpload).data;\n if (data === undefined) {\n throw new ValidationError(\n \"imessage\",\n \"Mini-app card image must be a Uint8Array, ArrayBuffer, Blob, or FileUpload\"\n );\n }\n return toImageBytes(data as MiniAppImage);\n}\n\nfunction requireField(value: string, field: string): string {\n if (!value || value.trim().length === 0) {\n throw new ValidationError(\n \"imessage\",\n `Mini-app card requires a non-empty \"${field}\"`\n );\n }\n return value;\n}\n\n/**\n * Validate and normalize a {@link MiniAppCard} into the\n * {@link CustomizedMiniAppInput} spectrum-ts's `customizedMiniApp()` builder\n * expects: required identifiers are checked non-empty, `url` is normalized to a\n * validated string, and the layout image (if any) is decoded to bytes.\n */\nexport async function resolveMiniApp(\n card: MiniAppCard\n): Promise<CustomizedMiniAppInput> {\n const url = typeof card.url === \"string\" ? card.url : card.url.toString();\n try {\n // Surface a clear error here rather than deep inside the builder's schema.\n new URL(url);\n } catch {\n throw new ValidationError(\n \"imessage\",\n `Mini-app card has an invalid url: \"${url}\"`\n );\n }\n\n const layout = card.layout ?? {};\n const image = layout.image ? await toImageBytes(layout.image) : undefined;\n\n return {\n appName: requireField(card.appName, \"appName\"),\n teamId: requireField(card.teamId, \"teamId\"),\n extensionBundleId: requireField(\n card.extensionBundleId,\n \"extensionBundleId\"\n ),\n url,\n ...(card.appStoreId === undefined ? {} : { appStoreId: card.appStoreId }),\n layout: {\n caption: layout.caption,\n subcaption: layout.subcaption,\n trailingCaption: layout.trailingCaption,\n trailingSubcaption: layout.trailingSubcaption,\n imageTitle: layout.imageTitle,\n imageSubtitle: layout.imageSubtitle,\n summary: layout.summary,\n ...(image ? { image } : {}),\n },\n };\n}\n\n/**\n * Distinguish the lightweight `app(url)` form from a fully-specified\n * {@link MiniAppCard}. An {@link AppUrl} is a string, a `Promise<string>`, or a\n * thunk (`() => string | Promise<string>`) — the mini-app card is a plain\n * object with named fields, so it never matches any of those shapes.\n */\nexport function isAppUrl(input: MiniAppCard | AppUrl): input is AppUrl {\n return (\n typeof input === \"string\" ||\n typeof input === \"function\" ||\n (typeof input === \"object\" &&\n input !== null &&\n typeof (input as PromiseLike<string>).then === \"function\")\n );\n}\n","import { ValidationError } from \"@chat-adapter/shared\";\nimport type { FileUpload } from \"chat\";\nimport { lookup as lookupMimeType } from \"mime-types\";\nimport { type ContentBuilder, voice as voiceContent } from \"spectrum-ts\";\n\n/**\n * Audio bytes for a voice message. Accepts raw bytes (`Uint8Array` / `Buffer` /\n * `ArrayBuffer`), a `Blob`, or a Chat SDK `FileUpload` — whatever your TTS\n * pipeline hands back gets normalized to the bytes spectrum-ts expects.\n */\nexport type VoiceBytes = Uint8Array | ArrayBuffer | Blob | FileUpload;\n\n/**\n * A voice message source: in-memory {@link VoiceBytes}, or an `http(s)` URL (a\n * `URL` or a string) that spectrum-ts fetches at send time. Local file paths\n * aren't accepted — read the file into bytes and pass those instead.\n */\nexport type VoiceInput = VoiceBytes | URL | string;\n\n/** Optional voice-message metadata. */\nexport interface VoiceOptions {\n /**\n * Playback duration in seconds, surfaced on the waveform bubble. Optional —\n * iMessage derives the waveform from the audio itself when omitted.\n */\n duration?: number;\n /**\n * MIME type of the audio (`audio/*`). Required for raw bytes when the `name`\n * carries no audio extension; inferred from the URL / name otherwise.\n */\n mimeType?: string;\n /** File name handed to spectrum-ts (also used to infer the MIME type). */\n name?: string;\n}\n\nconst AUDIO_MIME_PATTERN = /^audio\\//i;\n\n/**\n * Normalize accepted byte inputs to a fresh `Buffer`, alongside any name / MIME\n * hints the input carries. The copy detaches the bytes from any pooled/shared\n * backing buffer (e.g. a Node `Buffer`), matching how attachment and mini-app\n * bytes are handled — spectrum-ts reads them lazily at send time.\n */\nasync function toBytes(input: VoiceBytes): Promise<{\n bytes: Buffer;\n mimeHint?: string;\n nameHint?: string;\n}> {\n if (input instanceof Uint8Array) {\n // Buffer is a Uint8Array subclass, so this also covers Node Buffers.\n return { bytes: Buffer.from(input) };\n }\n if (input instanceof ArrayBuffer) {\n return { bytes: Buffer.from(input) };\n }\n if (input instanceof Blob) {\n return {\n bytes: Buffer.from(await input.arrayBuffer()),\n mimeHint: input.type || undefined,\n };\n }\n // Otherwise treat it as a Chat SDK FileUpload and unwrap its `data`.\n const data = (input as FileUpload).data;\n if (data === undefined) {\n throw new ValidationError(\n \"imessage\",\n \"Voice message must be a Uint8Array, ArrayBuffer, Blob, or FileUpload\"\n );\n }\n const nested = await toBytes(data as VoiceBytes);\n return {\n bytes: nested.bytes,\n mimeHint: (input as FileUpload).mimeType || nested.mimeHint,\n nameHint: (input as FileUpload).filename || nested.nameHint,\n };\n}\n\n/**\n * Resolve a URL source. A `URL` instance passes through; a string is parsed and\n * must be an `http(s)` URL. Returns `undefined` for byte inputs so the caller\n * falls through to the bytes path.\n */\nfunction toVoiceUrl(input: VoiceInput): URL | undefined {\n if (input instanceof URL) {\n return input;\n }\n if (typeof input !== \"string\") {\n return;\n }\n let url: URL;\n try {\n url = new URL(input);\n } catch {\n throw new ValidationError(\n \"imessage\",\n `Voice message string input must be an http(s) URL, got \"${input}\". Pass audio bytes for local files.`\n );\n }\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") {\n throw new ValidationError(\n \"imessage\",\n `Voice message URL must be http(s), got \"${url.protocol}\"`\n );\n }\n return url;\n}\n\n/**\n * Resolve an `audio/*` MIME type for a byte source from an explicit hint (the\n * caller's `mimeType`, or a Blob/FileUpload's own type) or by inference from\n * the name's extension. Throws a `ValidationError` when neither yields an audio\n * type — spectrum-ts requires one to render a playable voice note.\n */\nfunction resolveBytesMime(\n hint: string | undefined,\n name: string | undefined\n): string {\n if (hint && AUDIO_MIME_PATTERN.test(hint)) {\n return hint;\n }\n if (name) {\n const inferred = lookupMimeType(name);\n if (inferred && AUDIO_MIME_PATTERN.test(inferred)) {\n return inferred;\n }\n }\n throw new ValidationError(\n \"imessage\",\n hint\n ? `Voice message requires an audio/* MIME type, got \"${hint}\"`\n : 'Voice message requires an audio/* MIME type — pass options.mimeType (e.g. \"audio/mp4\") or a name with an audio extension'\n );\n}\n\n/**\n * Validate and normalize a voice source into the spectrum-ts `voice()` content\n * builder. URL sources are fetched by spectrum-ts at send time; byte sources\n * are copied to a detached `Buffer` and tagged with a resolved `audio/*` MIME\n * type. The builder is sent as a native iMessage voice note (a waveform\n * bubble), not an audio-file attachment.\n */\nexport async function resolveVoice(\n input: VoiceInput,\n options: VoiceOptions = {}\n): Promise<ContentBuilder> {\n if (options.mimeType && !AUDIO_MIME_PATTERN.test(options.mimeType)) {\n throw new ValidationError(\n \"imessage\",\n `Voice message requires an audio/* MIME type, got \"${options.mimeType}\"`\n );\n }\n\n const url = toVoiceUrl(input);\n if (url) {\n // spectrum-ts fetches the URL at send time and infers the MIME type from\n // its path when we don't override it.\n return voiceContent(url, {\n ...(options.mimeType ? { mimeType: options.mimeType } : {}),\n ...(options.name ? { name: options.name } : {}),\n ...(options.duration === undefined ? {} : { duration: options.duration }),\n });\n }\n\n const { bytes, mimeHint, nameHint } = await toBytes(input as VoiceBytes);\n const name = options.name ?? nameHint;\n const mimeType = resolveBytesMime(options.mimeType ?? mimeHint, name);\n return voiceContent(bytes, {\n mimeType,\n ...(name ? { name } : {}),\n ...(options.duration === undefined ? {} : { duration: options.duration }),\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","imessage","ValidationError","ValidationError","ValidationError","ValidationError","lookupMimeType","parseMarkdown","ValidationError","ValidationError","lookupMimeType","toBytes","resolveBytesMime","ValidationError","imessage","ValidationError","ValidationError"]}
|