96 lines
4.3 KiB
Markdown
96 lines
4.3 KiB
Markdown
# 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](../known-limitations.md#imessage-attributedbody-decoder-is-heuristic-not-a-full-typedstream-parser).
|
|
- `SyncManager.swift` — `final 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.swift` — `syncMessages`, `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.swift` — `IMessageSendTransport` 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](../known-limitations.md#imessage-attributedbody-is-not-parsed).
|
|
- 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).
|