macsync/docs/modules/imessage.md

4.3 KiB

iMessage (IMessageSync)

Purpose

Sync iMessage / SMS conversations and send messages from the web back to contacts via Messages.app.

Direction

Bidirectional. Inbound: read chat.db -> push to server. Outbound: poll the legacy icloud.send_queue table -> AppleScript-driven send via Messages.app.

OS surface

~/Library/Messages/chat.db (SQLite, accessed via GRDB) plus Messages.app via AppleScript for outbound. Requires Full Disk Access; module surfaces .fullDiskAccessRequired when missing (@packages/imessage/Sources/IMessageSync/SyncManager.swift:36, 132-133). Contacts are loaded via Contacts.framework (@packages/imessage/Sources/IMessageSync/Reader.swift:77).

Files

  • Reader.swift (@packages/imessage/Sources/IMessageSync/Reader.swift): class iMessageReader (line 64) — connect(), getConversations(), getMessages(conversationId:since:), loadContacts(). getMessages now invokes extractTextFromAttributedBody(_:) (line 371): when chat.db's text column is null/empty and attributedBody is non-nil, the byte-scan heuristic recovers plain text and substitutes it before sending. Limits of the heuristic — single text run only, no rich formatting — are documented in known-limitations.
  • SyncManager.swiftfinal class SyncManager: BaseSyncManager<SyncStats, SyncError> (line 44); 30s timer (line 93). Owns sendQueueClient: SendQueueClient<IMessageSendTransport> (lines 70-86), didStartSync / willStopSync (lines 170-176). Handles schema-version migrations via Self.syncSchemaVersion (line 47) and chunked payload retries (lines 331-354).
  • APIClient.swiftsyncMessages, syncContacts, getStats, getPendingSends, reportSendResult, resetSync (@packages/imessage/Sources/IMessageSync/APIClient.swift:14-108).
  • Sender.swift — AppleScript driver SendService; uses AppleScriptEscape (@packages/shared/Sources/MacSyncShared/Util/AppleScriptEscape.swift:13) for all string interpolation.
  • SendQueueAdapter.swiftIMessageSendTransport adapting the bespoke legacy send-queue endpoints into the generic SendQueueTransport protocol.
  • ChunkingStrategy.swift — adaptive batch sizing for large message + attachment payloads.

Timing

  • Read interval: 30s (@packages/imessage/Sources/IMessageSync/SyncManager.swift:93).
  • Outbound poll interval: 30s (@packages/imessage/Sources/IMessageSync/SyncManager.swift:74).
  • Inbound batch size: adaptive via ChunkingStrategy.createAdaptiveChunks (line 202).

Server surface

  • Entity tables: icloud.contacts, icloud.conversations, icloud.messages, icloud.send_queue (legacy) (src/server/src/app/server.ts:38-49).
  • Client routes (src/server/src/surfaces/client/imessage.ts):
    • POST /client/imessage/sync (line 71)
    • POST /client/imessage/contacts (line 77)
    • GET /client/imessage/stats (line 83)
    • GET /client/imessage/send-queue/pending (line 88)
    • POST /client/imessage/send-queue/:id/result (line 126)
    • POST /client/imessage/reset (line 172)
  • Web routes (src/server/src/surfaces/my/messages.ts):
    • GET /my/messages/conversations (line 11)
    • GET /my/messages/conversations/:id/messages (line 15)
    • GET /my/messages/last-sync (line 21)
  • Admin enqueue: POST /admin/send-queue/enqueue (src/server/src/surfaces/admin/send-queue.ts:15).

Web surface

  • Tab: /messages (web/src/App.tsx:57).
  • API helpers: web/src/api/messages.ts.

Known limitations

  • attributedBody is forwarded raw; macOS 13+ messages with no plaintext appear blank in the dashboard. See known-limitations.
  • AppleScript send can stall if Messages.app is unresponsive; manual restart required.
  • The send queue is on a bespoke schema (extra columns vs the generic <module>_send_queue); admin tooling must special-case it.

Tests (@packages/imessage/Tests/IMessageSyncTests/)

  • ChunkingTests.swift — payload sizing/splitting (hermetic).
  • SendValidationTests.swift — recipient/phone format (hermetic).
  • SyncPayloadTests.swift — Codable round-trips (hermetic).

Not covered by tests: chat.db reading, AppleScript sending, contacts permission (all require macOS Privacy permissions).