feat(imessage): receive mini-app content - #236
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (9)
📜 Recent review details⏰ Context from checks skipped due to timeout. (1)
🧰 Additional context used📓 Path-based instructions (4)**/*.{ts,tsx}📄 CodeRabbit inference engine (AGENTS.md)
Files:
**/*.{js,jsx,ts,tsx}📄 CodeRabbit inference engine (AGENTS.md)
Files:
**/*.{tsx,ts}📄 CodeRabbit inference engine (AGENTS.md)
Files:
**/*.{test,spec}.{js,jsx,ts,tsx}📄 CodeRabbit inference engine (AGENTS.md)
Files:
🔇 Additional comments (9)
📝 WalkthroughWalkthroughThe PR adds ChangesMini App support
Estimated code review effort: 3 (Moderate) | ~25 minutes Mergeability Score: ⚪ Minimal · up to This PR adds inbound iMessage mini-app mapping and related typed metadata while preserving identity-only cards and attachment behavior; the supplied checks and focused tests pass, so no actionable merge-blocking risk remains beyond normal review. Sequence Diagram(s)sequenceDiagram
participant IMessageEvent
participant buildUnwrappedContentMessage
participant toMiniAppContent
participant Attachment
participant asResolvedApp
IMessageEvent->>buildUnwrappedContentMessage: provide Mini App metadata and attachments
buildUnwrappedContentMessage->>toMiniAppContent: convert Mini App content
toMiniAppContent->>Attachment: load first image attachment when needed
Attachment-->>toMiniAppContent: image bytes
toMiniAppContent->>asResolvedApp: construct app from URL and layout
asResolvedApp-->>toMiniAppContent: resolved app content
toMiniAppContent-->>buildUnwrappedContentMessage: Mini App content
Possibly related PRs
Suggested labels: Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
@spectrum-ts/core
@spectrum-ts/elysia
@spectrum-ts/express
@spectrum-ts/fastify
@spectrum-ts/hono
@spectrum-ts/imessage-local
@spectrum-ts/imessage
@spectrum-ts/slack
spectrum-ts
@spectrum-ts/telegram
@spectrum-ts/terminal
@spectrum-ts/whatsapp-business
commit: |
There was a problem hiding this comment.
Problem vs fix review
What problem is this trying to solve?
Advanced iMessage server v11.3.0 started sending real mini-app card data on inbound messages (Message.content.mini_app: URL, layout text, live state, app identity, etc.).
Spectrum could not use that data yet:
- the generated client was too old to decode the field, and
- even with the field present, the iMessage inbound mapper had no path for it, so these messages showed up as unsupported content.
In short: the wire format grew a new message type, and Spectrum’s receive path had not caught up.
Is that problem valid?
Yes. This is a real receive gap, not a cosmetic cleanup. Without this PR, inbound mini-app cards are effectively dropped into unsupported-message, even though the server already decoded the useful public card fields. The live validation note (Jump Jump arriving as content.type === "app") matches that.
Is the proposed fix the right solution, or only a symptom patch?
This is the right fix for the real underlying issue, not a bandaid.
The deeper issue was not “something random fails for mini-apps.” It was that Spectrum’s app content model was authored around outbound URL → fetch Open Graph metadata → invent a layout. That works when you create a card from a URL. It is the wrong model for inbound Apple cards, where the layout was already chosen by the sender/extension.
This PR fixes that at the right layers:
- Dependency bump to
@photon-ai/advanced-imessage@^2.1.0so the field is actually decoded instead of discarded. asResolvedAppso inbound adapters can buildappcontent from already-decoded URL/layout without refetching or inventing webpage metadata.- Inbound mapping that turns renderable mini-app cards into universal
appcontent, while keeping Apple-specific identity (bundle ID, team ID, App Store ID, session, etc.) on native metadata. - Identity-only cards stay as metadata without fabricating a fake renderable card.
- Lazy image hydration from the native attachment, so image bytes are loaded only if someone reads the layout.
That is the correct shape: decode → map to Spectrum’s shared content type → preserve native extras separately → do not rewrite the card by scraping the URL.
Small residual notes (not blockers)
These do not change the verdict, but are worth knowing:
- Inbound layout validation is intentionally looser.
asResolvedAppaccepts the base layout object and skips the stricter “must have a visible slot / image+title pairing” checks used when authoring apps. That fits “trust what Apple sent,” but consumers should not assume every inboundapplayout is as strict as an outbound one. - Image picking uses the first
image/*attachment. That is reasonable for normal mini-app cards; it could be wrong if a future mini-app message also carries unrelated images. - Dual surface is deliberate and good:
contentis the cross-provider card (app), whilemessage.miniAppkeeps iMessage-native identity. That avoids stuffing Apple-only fields into the universal content type.
Verdict
Valid problem. Right solution. This closes a real protocol/adapter gap after the server started emitting semantic mini-app fields. The important design choice — use decoded native layout via asResolvedApp instead of refetching URL metadata — addresses the actual model mismatch, not just the symptom of “unsupported message.”
Sent by Cursor Automation: PR analyze
| export const asResolvedApp = ( | ||
| url: string, | ||
| layout: AppLayout | (() => Promise<AppLayout>), | ||
| options: AppOptions = {} | ||
| ): App => { | ||
| const resolvedUrl = z.url().parse(url); | ||
| const getLayout = memoize(async () => | ||
| resolvedAppLayoutSchema.parse( | ||
| typeof layout === "function" ? await layout() : layout |
There was a problem hiding this comment.
This is the key root-cause fix, not a workaround: inbound native cards already have a decoded layout, so reusing asApp (URL scrape / Open Graph) would invent or overwrite sender-provided card data.
One intentional tradeoff here: this path validates with the looser base schema and skips the stricter authoring refinements (visible slot required, image/title pairing). That seems right for “preserve what Apple sent,” but inbound app layouts may be weaker than outbound ones.
| const toMiniAppContent = ( | ||
| client: AdvancedIMessage, | ||
| message: AppleMessage | ||
| ): Content | undefined => { | ||
| const miniApp = message.content.miniApp; | ||
| if (!(miniApp?.url && miniApp.layout)) { | ||
| return; | ||
| } | ||
|
|
||
| const imageAttachment = messageAttachments(message).find((attachment) => | ||
| normalizeAppleAttachmentMimeType(attachment).startsWith("image/") | ||
| ); |
There was a problem hiding this comment.
Good receive mapping overall: require both url and layout before creating renderable app content, and leave identity-only mini-apps as metadata.
Minor edge case: image bytes come from the first image/* attachment. Fine for typical mini-app cards; worth watching if Apple ever attaches extra images alongside the card art.


Summary
appcontentWhy
advanced-imessage-server v11.3.0 now emits semantic
Message.content.mini_appfields. Spectrum previously received these messages as unsupported content because its generated client discarded the new field and its iMessage adapter had no app-content mapping.This keeps Apple's opaque payload archive private while giving Spectrum consumers the public semantic card data.
Dependency
Uses
@photon-ai/advanced-imessage@^2.1.0, published from photon-hq/advanced-imessage-ts#52.Validation
Validated from a clean
bun install --frozen-lockfileagainst the published@photon-ai/advanced-imessage@2.1.0:bun run checkbun run typecheck— 13/13 tasksbun run test— 32/32 Node and Bun tasksbun run build— 12/12 taskscontent.type === "app"with its URL, caption, subcaption, summary, identity, App Store ID, and live state intactDependency rollout
Summary by CodeRabbit
New Features
Bug Fixes
Tests