fix: clarify Tahoe typing failures
This commit is contained in:
parent
9ec34e69fb
commit
715a75fb4e
@ -23,6 +23,7 @@
|
||||
- fix: dedupe URL balloon preview duplicates in watch stream without cross-chat/schema regressions (#64, thanks @lesaai)
|
||||
- fix: normalize IMCore typing chat lookup across `iMessage`, `SMS`, and `any` prefixes (#51, #54, #56, #58)
|
||||
- docs: document macOS 26 advanced IMCore injection limits (#60)
|
||||
- fix: report macOS 26/Tahoe IMCore typing entitlement failures as advanced-feature setup errors (#60)
|
||||
- docs: add a local release helper for dispatching Homebrew tap updates (#97, thanks @dinakars777)
|
||||
- feat: resolve contact names in chat/message output and direct sends (#75, #77, thanks @regaw-leinad and @jsindy)
|
||||
|
||||
|
||||
@ -144,6 +144,9 @@ Important:
|
||||
- macOS 26 can also block Messages.app dylib injection with library validation.
|
||||
In that case `imsg status` reports advanced features unavailable even with SIP
|
||||
disabled; normal send/history/watch commands still work.
|
||||
- On macOS 26/Tahoe, direct IMCore access can also fail because `imagent`
|
||||
rejects clients without Apple-private entitlements. That only affects advanced
|
||||
IMCore features such as `typing`.
|
||||
|
||||
Setup:
|
||||
1) Disable SIP from Recovery mode: `csrutil disable`
|
||||
|
||||
@ -23,8 +23,9 @@ public enum IMCoreBridgeError: Error, CustomStringConvertible {
|
||||
/// Bridge to IMCore via DYLD injection into Messages.app.
|
||||
///
|
||||
/// Communicates with an injected dylib inside Messages.app via file-based IPC.
|
||||
/// The dylib has full access to IMCore because it runs within the Messages.app
|
||||
/// context with proper entitlements.
|
||||
/// The dylib has access to IMCore when Messages.app accepts the injection.
|
||||
/// macOS 26/Tahoe can still block this path with library validation/private
|
||||
/// entitlement checks.
|
||||
///
|
||||
/// Requires:
|
||||
/// - SIP disabled (for `DYLD_INSERT_LIBRARIES` on system apps)
|
||||
@ -139,6 +140,10 @@ public final class IMCoreBridge: @unchecked Sendable {
|
||||
"""
|
||||
SIP is disabled and the helper dylib is present, but Messages.app is not currently injected.
|
||||
Run `imsg launch` to enable advanced IMCore features.
|
||||
|
||||
Note: macOS 26/Tahoe can still block advanced IMCore features through
|
||||
library validation or imagent private entitlement checks. Basic send,
|
||||
history, and watch commands do not use this path.
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
@ -134,6 +134,7 @@ public struct TypingIndicator: Sendable {
|
||||
if hasLiveDaemonConnection(controller) {
|
||||
daemonConnectionTracker.lock.lock()
|
||||
daemonConnectionTracker.hasAttemptedConnection = true
|
||||
daemonConnectionTracker.connectionKnownUnavailable = false
|
||||
daemonConnectionTracker.lock.unlock()
|
||||
return
|
||||
}
|
||||
@ -154,6 +155,9 @@ public struct TypingIndicator: Sendable {
|
||||
let maxAttempts = 50
|
||||
for _ in 0..<maxAttempts {
|
||||
if hasLiveDaemonConnection(controller) {
|
||||
daemonConnectionTracker.lock.lock()
|
||||
daemonConnectionTracker.connectionKnownUnavailable = false
|
||||
daemonConnectionTracker.lock.unlock()
|
||||
return
|
||||
}
|
||||
Thread.sleep(forTimeInterval: 0.1)
|
||||
@ -161,11 +165,11 @@ public struct TypingIndicator: Sendable {
|
||||
}
|
||||
|
||||
if !hasLiveDaemonConnection(controller) {
|
||||
daemonConnectionTracker.lock.lock()
|
||||
daemonConnectionTracker.connectionKnownUnavailable = true
|
||||
daemonConnectionTracker.lock.unlock()
|
||||
throw IMsgError.typingIndicatorFailed(
|
||||
"Failed to connect to imagent (iMessage daemon). "
|
||||
+ "This requires either SIP disabled with 'imsg launch', "
|
||||
+ "or system modifications (AMFI disabled + XPC plist). "
|
||||
+ "Run 'imsg status' for setup instructions."
|
||||
daemonUnavailableMessage()
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -221,11 +225,27 @@ public struct TypingIndicator: Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
daemonConnectionTracker.lock.lock()
|
||||
let connectionKnownUnavailable = daemonConnectionTracker.connectionKnownUnavailable
|
||||
daemonConnectionTracker.lock.unlock()
|
||||
if connectionKnownUnavailable {
|
||||
throw IMsgError.typingIndicatorFailed(daemonUnavailableMessage())
|
||||
}
|
||||
|
||||
throw IMsgError.typingIndicatorFailed(
|
||||
"Chat not found for identifier: \(identifier). "
|
||||
+ "Make sure Messages.app has an active conversation with this contact.")
|
||||
}
|
||||
|
||||
static func daemonUnavailableMessage() -> String {
|
||||
"Failed to connect to imagent (Messages daemon) for IMCore typing indicators. "
|
||||
+ "On macOS 26/Tahoe, imagent can reject third-party clients without "
|
||||
+ "Apple-private entitlements, and Messages.app may also block the injected "
|
||||
+ "bridge via library validation. Run 'imsg status' and 'imsg launch' to "
|
||||
+ "verify advanced feature setup. Normal 'send', 'history', and 'watch' "
|
||||
+ "commands do not use this IMCore path."
|
||||
}
|
||||
|
||||
static func chatLookupCandidates(for identifier: String) -> [String] {
|
||||
let trimmed = identifier.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard !trimmed.isEmpty else { return [] }
|
||||
@ -272,6 +292,7 @@ public struct TypingIndicator: Sendable {
|
||||
private final class DaemonConnectionTracker: @unchecked Sendable {
|
||||
let lock = NSLock()
|
||||
var hasAttemptedConnection = false
|
||||
var connectionKnownUnavailable = false
|
||||
}
|
||||
|
||||
/// Thread-safe box for passing an error out of a Task back to the calling thread.
|
||||
|
||||
@ -81,6 +81,14 @@ enum StatusCommand {
|
||||
StdoutWriter.writeLine(" make build-dylib")
|
||||
StdoutWriter.writeLine(" imsg launch")
|
||||
StdoutWriter.writeLine("")
|
||||
StdoutWriter.writeLine("macOS 26/Tahoe note:")
|
||||
StdoutWriter.writeLine(
|
||||
" Advanced IMCore features may still be blocked by library validation"
|
||||
)
|
||||
StdoutWriter.writeLine(
|
||||
" or imagent private entitlement checks. Basic commands still work."
|
||||
)
|
||||
StdoutWriter.writeLine("")
|
||||
StdoutWriter.writeLine("Note: Basic messaging features work without these steps.")
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,3 +78,16 @@ func typingLookupCandidatesAvoidDoublePrefixingDirectIdentifiers() {
|
||||
func typingLookupCandidatesRejectBlankIdentifier() {
|
||||
#expect(TypingIndicator.chatLookupCandidates(for: " ").isEmpty)
|
||||
}
|
||||
|
||||
@Test
|
||||
func typingDaemonUnavailableMessageExplainsTahoeEntitlementBlock() {
|
||||
let message = TypingIndicator.daemonUnavailableMessage()
|
||||
|
||||
#expect(message.contains("imagent"))
|
||||
#expect(message.contains("macOS 26/Tahoe"))
|
||||
#expect(message.contains("Apple-private entitlements"))
|
||||
#expect(message.contains("imsg status"))
|
||||
#expect(message.contains("send"))
|
||||
#expect(message.contains("history"))
|
||||
#expect(message.contains("watch"))
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user