feat(imessage): outbound over the HTTP middleware - #186
Conversation
|
|
📝 WalkthroughWalkthroughThe iMessage package upgrades ChangesiMessage client plane separation
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant RemoteMessages as messages
participant ClientStream as clientStream
participant GrpcStreams as gRPC streams
participant EventSource as iMessage event source
RemoteMessages->>ClientStream: pass RemoteClient entry
ClientStream->>GrpcStreams: subscribeEvents()
GrpcStreams->>EventSource: receive inbound events
EventSource-->>GrpcStreams: deliver events
GrpcStreams-->>ClientStream: emit mapped events
Possibly related PRs
Suggested labels: Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
… gRPC streams
Outbound unary calls move off the per-instance gRPC hosts onto the HTTP
middleware (imessage-server-v2-http) via advanced-imessage 2.0's fetch
transport. Each RemoteClient now carries two clients:
- client: createHttpClient dialing SPECTRUM_IMESSAGE_HTTP_ADDRESS
(default imessage.spectrum.photon.codes:443); dedicated lines pass
server: instanceId so the middleware routes via x-photon-server
instead of dialing {instanceId}.imsg.photon.codes directly.
- streams: createGrpcClient, unchanged addresses — serves only
subscribeEvents/events.catchUp until Fusor inbound (#195) lands.
Mapper RPCs inside the stream path (message/attachment rebuilds) ride
the HTTP client too, so the gRPC plane is subscription-only. Explicit
clients reuse their single configured address for both transports.
bun.lock intentionally untouched: 2.0.0 is unpublished, resolved via a
local symlink to advanced-imessage-ts#47 (feat/http-client-combined,
a053778).
Entire-Checkpoint: ac48d1a2d0c6
56f2ab8 to
7805ad6
Compare
| "dependencies": { | ||
| "@photon-ai/advanced-imessage": "^1.0.0", | ||
| "@photon-ai/advanced-imessage": "^2.0.0", | ||
| "@photon-ai/otel": "^3.1.0", |
There was a problem hiding this comment.
SDK 2.0 makes nice-grpc, nice-grpc-common, and @grpc/grpc-js optional peers, and createGrpcClient fails fast if they are missing. This package still creates a gRPC streams client for every RemoteClient, but only bumps @photon-ai/advanced-imessage to ^2.0.0 — it never declares those gRPC packages.
For the dual-client transition to actually work for consumers (not just a local symlink), @spectrum-ts/imessage likely needs to depend on / peer-depend on those three packages until #195 removes the gRPC stream plane.
There was a problem hiding this comment.
worry about this in #195 if grpc there is still needed.
| @@ -488,6 +492,11 @@ export const imessage = definePlatform("iMessage", { | |||
| tls: true, | |||
| token: e.token, | |||
| }), | |||
| streams: createGrpcClient({ | |||
There was a problem hiding this comment.
Cloud mode correctly splits HTTP vs gRPC addresses. Explicit clients do not: both createHttpClient and createGrpcClient reuse e.address.
That is workable during the transition only if the configured host really serves both planes. If someone points this at a pure HTTP middleware host (or a pure gRPC host), one side will break while the other looks fine. Consider either documenting this as a hard config requirement in the public API docs, or temporarily allowing separate outbound/stream addresses here the same way cloud env vars do.
There was a problem hiding this comment.
this is only b/c we are transitioning, #195 will fix this.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/imessage/src/index.ts (1)
514-516: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winPrefer
Promise.allSettledfor resource cleanup.Using
Promise.allfor mass disposal can lead to unhandled promise rejections if oneclose()call rejects while others are still pending.Promise.allSettledensures all clients and streams attempt to close gracefully without short-circuiting on the first failure.♻️ Proposed refactor
- await Promise.all( + await Promise.allSettled( client.flatMap((entry) => [entry.client.close(), entry.streams.close()]) );🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/imessage/src/index.ts` around lines 514 - 516, Update the resource cleanup call in the client shutdown flow to use Promise.allSettled instead of Promise.all, while preserving the existing entry.client.close() and entry.streams.close() operations so every client and stream close attempt completes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@packages/imessage/src/index.ts`:
- Around line 514-516: Update the resource cleanup call in the client shutdown
flow to use Promise.allSettled instead of Promise.all, while preserving the
existing entry.client.close() and entry.streams.close() operations so every
client and stream close attempt completes.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 4fcf8669-526e-4cd3-97fc-1c7cecee573d
📒 Files selected for processing (11)
packages/imessage/package.jsonpackages/imessage/src/auth.tspackages/imessage/src/index.tspackages/imessage/src/remote/stream.tspackages/imessage/src/types.tspackages/imessage/test/contact-card.test.tspackages/imessage/test/membership.test.tspackages/imessage/test/read-actions.test.tspackages/imessage/test/remote/app.test.tspackages/imessage/test/remote/stream.test.tspackages/imessage/test/space.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
- GitHub Check: Cursor Bugbot
⚠️ CI failures not shown inline (8)
GitHub Actions: Preview / preview: feat(imessage): outbound over the HTTP middleware
Conclusion: failure
##[group]Run bun install --frozen-lockfile
�[36;1mbun install --frozen-lockfile�[0m
shell: /usr/bin/bash -e {0}
##[endgroup]
bun install v1.3.14 (0d9b296a)
Resolving dependencies
Resolved, downloaded and extracted [60]
error: No version matching "^2.0.0" found for specifier "`@photon-ai/advanced-imessage`" (but package exists)
error: `@photon-ai/advanced-imessage`@^2.0.0 failed to resolve
##[error]Process completed with exit code 1.
GitHub Actions: Preview / 0_preview.txt: feat(imessage): outbound over the HTTP middleware
Conclusion: failure
##[group]Run bun install --frozen-lockfile
�[36;1mbun install --frozen-lockfile�[0m
shell: /usr/bin/bash -e {0}
##[endgroup]
bun install v1.3.14 (0d9b296a)
Resolving dependencies
Resolved, downloaded and extracted [60]
error: No version matching "^2.0.0" found for specifier "`@photon-ai/advanced-imessage`" (but package exists)
error: `@photon-ai/advanced-imessage`@^2.0.0 failed to resolve
##[error]Process completed with exit code 1.
GitHub Actions: CI / 0_test (node).txt: feat(imessage): outbound over the HTTP middleware
Conclusion: failure
##[group]Run bun install --frozen-lockfile
�[36;1mbun install --frozen-lockfile�[0m
shell: /usr/bin/bash -e {0}
##[endgroup]
bun install v1.3.14 (0d9b296a)
Resolving dependencies
Resolved, downloaded and extracted [60]
error: No version matching "^2.0.0" found for specifier "`@photon-ai/advanced-imessage`" (but package exists)
error: `@photon-ai/advanced-imessage`@^2.0.0 failed to resolve
##[error]Process completed with exit code 1.
GitHub Actions: CI / test (node): feat(imessage): outbound over the HTTP middleware
Conclusion: failure
##[group]Run bun install --frozen-lockfile
�[36;1mbun install --frozen-lockfile�[0m
shell: /usr/bin/bash -e {0}
##[endgroup]
bun install v1.3.14 (0d9b296a)
Resolving dependencies
Resolved, downloaded and extracted [60]
error: No version matching "^2.0.0" found for specifier "`@photon-ai/advanced-imessage`" (but package exists)
error: `@photon-ai/advanced-imessage`@^2.0.0 failed to resolve
##[error]Process completed with exit code 1.
GitHub Actions: CI / test (bun): feat(imessage): outbound over the HTTP middleware
Conclusion: failure
##[group]Run bun install --frozen-lockfile
�[36;1mbun install --frozen-lockfile�[0m
shell: /usr/bin/bash -e {0}
##[endgroup]
bun install v1.3.14 (0d9b296a)
Resolving dependencies
Resolved, downloaded and extracted [60]
error: No version matching "^2.0.0" found for specifier "`@photon-ai/advanced-imessage`" (but package exists)
error: `@photon-ai/advanced-imessage`@^2.0.0 failed to resolve
##[error]Process completed with exit code 1.
GitHub Actions: CI / check: feat(imessage): outbound over the HTTP middleware
Conclusion: failure
##[group]Run bun install --frozen-lockfile
�[36;1mbun install --frozen-lockfile�[0m
shell: /usr/bin/bash -e {0}
##[endgroup]
bun install v1.3.14 (0d9b296a)
Resolving dependencies
Resolved, downloaded and extracted [60]
error: No version matching "^2.0.0" found for specifier "`@photon-ai/advanced-imessage`" (but package exists)
error: `@photon-ai/advanced-imessage`@^2.0.0 failed to resolve
##[error]Process completed with exit code 1.
GitHub Actions: CI / 1_check.txt: feat(imessage): outbound over the HTTP middleware
Conclusion: failure
##[group]Run bun install --frozen-lockfile
�[36;1mbun install --frozen-lockfile�[0m
shell: /usr/bin/bash -e {0}
##[endgroup]
bun install v1.3.14 (0d9b296a)
Resolving dependencies
Resolved, downloaded and extracted [60]
error: No version matching "^2.0.0" found for specifier "`@photon-ai/advanced-imessage`" (but package exists)
error: `@photon-ai/advanced-imessage`@^2.0.0 failed to resolve
##[error]Process completed with exit code 1.
GitHub Actions: CI / 2_test (bun).txt: feat(imessage): outbound over the HTTP middleware
Conclusion: failure
##[group]Run bun install --frozen-lockfile
�[36;1mbun install --frozen-lockfile�[0m
shell: /usr/bin/bash -e {0}
##[endgroup]
bun install v1.3.14 (0d9b296a)
Resolving dependencies
Resolved, downloaded and extracted [60]
error: No version matching "^2.0.0" found for specifier "`@photon-ai/advanced-imessage`" (but package exists)
error: `@photon-ai/advanced-imessage`@^2.0.0 failed to resolve
##[error]Process completed with exit code 1.
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use explicit function parameter and return types when they improve clarity; prefer
unknownoverany; useas constfor immutable literal values; and rely on TypeScript narrowing instead of assertions.
Files:
packages/imessage/test/contact-card.test.tspackages/imessage/test/space.test.tspackages/imessage/test/membership.test.tspackages/imessage/test/remote/app.test.tspackages/imessage/test/remote/stream.test.tspackages/imessage/test/read-actions.test.tspackages/imessage/src/auth.tspackages/imessage/src/index.tspackages/imessage/src/types.tspackages/imessage/src/remote/stream.ts
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{js,jsx,ts,tsx}: Use meaningful variable names and extract magic numbers into descriptively named constants.
Use arrow functions for callbacks and short functions.
Preferfor...ofloops over.forEach()and indexedforloops.
Use optional chaining and nullish coalescing for safer property access.
Prefer template literals over string concatenation and use destructuring for object and array assignments.
Useconstby default,letonly when reassignment is needed, and never usevar.
Always await promises in async functions and use the returned value; prefer async/await over promise chains.
Handle async errors appropriately with try-catch blocks and do not use async functions as Promise executors.
Removeconsole.log,debugger, andalertstatements from production code.
ThrowErrorobjects with descriptive messages rather than strings or other values.
Use try-catch blocks meaningfully and do not catch errors solely to rethrow them.
Prefer early returns for error cases and to reduce nesting; use simple conditionals instead of nested ternaries.
Keep functions focused and within reasonable cognitive-complexity limits, extract complex conditions into named booleans, and group related code while separating concerns.
AvoiddangerouslySetInnerHTMLunless absolutely necessary; do not useeval()or assign directly todocument.cookie; validate and sanitize user input.
Avoid spread syntax in accumulators within loops, use top-level regex literals instead of creating them in loops, prefer specific imports over namespace imports, and avoid barrel files that re-export everything.
Files:
packages/imessage/test/contact-card.test.tspackages/imessage/test/space.test.tspackages/imessage/test/membership.test.tspackages/imessage/test/remote/app.test.tspackages/imessage/test/remote/stream.test.tspackages/imessage/test/read-actions.test.tspackages/imessage/src/auth.tspackages/imessage/src/index.tspackages/imessage/src/types.tspackages/imessage/src/remote/stream.ts
**/*.{tsx,ts}
📄 CodeRabbit inference engine (AGENTS.md)
In Next.js, use Server Components for async data fetching instead of async Client Components.
Files:
packages/imessage/test/contact-card.test.tspackages/imessage/test/space.test.tspackages/imessage/test/membership.test.tspackages/imessage/test/remote/app.test.tspackages/imessage/test/remote/stream.test.tspackages/imessage/test/read-actions.test.tspackages/imessage/src/auth.tspackages/imessage/src/index.tspackages/imessage/src/types.tspackages/imessage/src/remote/stream.ts
**/*.{test,spec}.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{test,spec}.{js,jsx,ts,tsx}: Write assertions insideit()ortest()blocks, avoid done callbacks in async tests, and do not commit tests containing.onlyor.skip.
Keep test suites reasonably flat and avoid excessivedescribenesting.
Files:
packages/imessage/test/contact-card.test.tspackages/imessage/test/space.test.tspackages/imessage/test/membership.test.tspackages/imessage/test/remote/app.test.tspackages/imessage/test/remote/stream.test.tspackages/imessage/test/read-actions.test.ts
🔇 Additional comments (11)
packages/imessage/package.json (1)
48-48: LGTM!packages/imessage/src/auth.ts (1)
1-4: LGTM!Also applies to: 166-199, 211-230
packages/imessage/src/index.ts (1)
3-4: LGTM!Also applies to: 480-485, 495-499
packages/imessage/src/remote/stream.ts (1)
5-5: LGTM!Also applies to: 228-237, 285-291, 318-329, 353-363, 380-400, 425-426
packages/imessage/src/types.ts (1)
1-16: LGTM!packages/imessage/test/contact-card.test.ts (1)
1-14: LGTM!Also applies to: 34-34
packages/imessage/test/membership.test.ts (1)
1-4: LGTM!Also applies to: 15-17, 37-41
packages/imessage/test/read-actions.test.ts (1)
2-4: LGTM!Also applies to: 17-19, 82-86
packages/imessage/test/remote/app.test.ts (1)
3-3: LGTM!Also applies to: 14-16, 58-58
packages/imessage/test/remote/stream.test.ts (1)
1-4: LGTM!Also applies to: 49-54, 140-146, 215-220
packages/imessage/test/space.test.ts (1)
1-12: LGTM!Also applies to: 47-51
…with an optional server id Explicit config.clients kept feeding their single gRPC address to the HTTP client, but per-instance hosts speak only gRPC (grpc-swift) — the middleware is the sole HTTP front door. Outbound now dials the same SPECTRUM_IMESSAGE_HTTP_ADDRESS middleware as the cloud path, `address` keeps its historical meaning (the gRPC stream plane), and a new optional `server` config field carries the dedicated instance id (x-photon-server) so static-token dedicated setups can route. Flagged by Cursor Bugbot on #186. Entire-Checkpoint: a5d9ae664be9
…ay self-contained Explicit clients could only reach the middleware via the env default, which silently pointed self-hosted setups at the production host. `httpAddress` overrides SPECTRUM_IMESSAGE_HTTP_ADDRESS per entry; `address` stays the gRPC plane and `server` the routing id. Flagged by capy-ai on #186.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit be062a9. Configure here.

Moves iMessage outbound off the per-instance gRPC hosts and onto the HTTP middleware (
imessage-server-v2-http) via advanced-imessage 2.0's fetch transport.Inbound is not this PR's concern — it stays exactly as on main and moves to Fusor in #195.
What changed
createHttpClient. Both modes dial one middleware host:SPECTRUM_IMESSAGE_HTTP_ADDRESS, defaultimessage.spectrum.photon.codes:443.{instanceId}.imsg.photon.codesdirectly — they passserver: instanceId, sent asx-photon-server, and the middleware routes server-side.autoIdempotency+retry, idempotency-keyed so retries can't double-apply.addresskeeps its historical meaning. Outbound rides the middleware — per-entry optionalhttpAddress(falls back toSPECTRUM_IMESSAGE_HTTP_ADDRESS/default) keeps self-hosted configs self-contained, and optionalservercarries the dedicated instance id for routing.Dependency / draft blocker
Bumps
@photon-ai/advanced-imessageto^2.0.0— unpublished; requires advanced-imessage-ts #47 (feat/http-client-combined, pinned locally ata053778).bun.lockis intentionally untouched until 2.0.0 is released; local dev resolves the SDK via symlink.Validation
bun run typecheck(all 13 packages)bun x ultracite checkpackages/imessage: 142 tests viavitest(node + bun runners)Note
Medium Risk
Splits transport for all outbound iMessage actions and changes dedicated routing to middleware headers; depends on unpublished SDK 2.0, so regressions in send/retry or streaming are possible until release.
Overview
Outbound iMessage calls now go through the HTTP middleware (
imessage-server-v2-http) via@photon-ai/advanced-imessage2.0 (createHttpClient), instead of unary gRPC on per-instance hosts. Shared and dedicated cloud clients dial one middleware host (SPECTRUM_IMESSAGE_HTTP_ADDRESS, defaultimessage.spectrum.photon.codes:443); dedicated lines passserver: instanceIdfor middleware routing instead of calling{instanceId}.imsg.photon.codesfor sends. Inbound is unchanged in behavior but wired through a newstreamsgRPC client on eachRemoteClient; catch-up and live subscriptions usestreams, while send/edit/react and other outbound paths still useclient.Explicit static-token config keeps
addressfor the gRPC event plane and adds optionalhttpAddressandserver. Teardown closes both HTTP and gRPC clients. Tests and config schema cover the new fields.Reviewed by Cursor Bugbot for commit be062a9. Bugbot is set up for automated code reviews on this repo. Configure here.
Summary by CodeRabbit
New Features
Bug Fixes
Tests
Chores