116 lines
3.4 KiB
Swift
116 lines
3.4 KiB
Swift
import Foundation
|
|
import LilithLogging
|
|
import MacSyncShared
|
|
|
|
private let log = AppLogger.logger(for: "IMail.Sender")
|
|
|
|
// MARK: - Wire types
|
|
|
|
public struct MailSendPayload: Sendable, Decodable, Equatable {
|
|
public let to: String
|
|
public let cc: [String]?
|
|
public let bcc: [String]?
|
|
public let subject: String
|
|
public let body: String
|
|
public let isHtml: Bool?
|
|
|
|
public init(
|
|
to: String,
|
|
cc: [String]? = nil,
|
|
bcc: [String]? = nil,
|
|
subject: String,
|
|
body: String,
|
|
isHtml: Bool? = nil
|
|
) {
|
|
self.to = to
|
|
self.cc = cc
|
|
self.bcc = bcc
|
|
self.subject = subject
|
|
self.body = body
|
|
self.isHtml = isHtml
|
|
}
|
|
}
|
|
|
|
public struct PendingMailSend: Sendable, Decodable, Equatable {
|
|
public let id: String
|
|
public let action: String
|
|
public let payload: MailSendPayload
|
|
public let createdAt: String
|
|
|
|
public init(id: String, action: String, payload: MailSendPayload, createdAt: String) {
|
|
self.id = id
|
|
self.action = action
|
|
self.payload = payload
|
|
self.createdAt = createdAt
|
|
}
|
|
}
|
|
|
|
// MARK: - Applier protocol (hermetic-test seam)
|
|
|
|
/// Narrow protocol the `MailSender` calls into so tests can substitute a
|
|
/// fake that does not require Mail.app / AppleScript automation access.
|
|
@MainActor
|
|
public protocol MailSendApplying: Sendable {
|
|
func send(_ payload: MailSendPayload) async -> SendQueueApplyResult
|
|
}
|
|
|
|
// MARK: - Sender
|
|
|
|
/// Default `MailSendApplying` backed by the existing AppleScript-based
|
|
/// `Sender` (Mail.app driver). Adapts the structured `MailSendPayload` to
|
|
/// `Sender.SendRequest` and dispatches the synchronous AppleScript call off
|
|
/// the main thread.
|
|
@MainActor
|
|
public final class MailSender: MailSendApplying {
|
|
private let sender: Sender
|
|
|
|
public init(sender: Sender = .shared) {
|
|
self.sender = sender
|
|
}
|
|
|
|
public func send(_ payload: MailSendPayload) async -> SendQueueApplyResult {
|
|
let request = Sender.SendRequest(
|
|
to: [payload.to],
|
|
cc: payload.cc ?? [],
|
|
bcc: payload.bcc ?? [],
|
|
subject: payload.subject,
|
|
body: payload.body,
|
|
isHTML: payload.isHtml ?? false
|
|
)
|
|
// `Sender.send` runs a blocking AppleScript call — dispatch off main.
|
|
let result = await Task.detached(priority: .userInitiated) { [sender] in
|
|
sender.send(request)
|
|
}.value
|
|
|
|
if result.success {
|
|
return .sent
|
|
} else {
|
|
let reason = result.error ?? "unknown send failure"
|
|
log.warning("MailSender.send failed: \(reason)")
|
|
return .failed(reason: reason)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - SendQueueTransport adapter
|
|
|
|
public struct IMailSendTransport: SendQueueTransport {
|
|
public typealias PendingItem = PendingMailSend
|
|
|
|
private let apiClient: any IMailAPIClientProtocol
|
|
|
|
public init(apiClient: any IMailAPIClientProtocol) {
|
|
self.apiClient = apiClient
|
|
}
|
|
|
|
public func id(of item: PendingMailSend) -> String { item.id }
|
|
|
|
public func fetchPending() async throws -> [PendingMailSend] {
|
|
guard apiClient.isAuthenticated else { return [] }
|
|
return try await apiClient.getPendingSends()
|
|
}
|
|
|
|
public func reportResult(id: String, status: String, error: String?) async throws {
|
|
try await apiClient.reportSendResult(id: id, status: status, error: error)
|
|
}
|
|
}
|