forked from Manuel/meeting-assistant
ci: consolidate platform pipelines and test prerequisites on macOS support
This commit is contained in:
1 parent
1b19b08f2e
commit
728e66dd72
76 files changed
+7477
-239
No files matched your search
@@ -0,0 +1,115 @@
|
||||
import Foundation
|
||||
import UserNotifications
|
||||
|
||||
struct DesktopUserAction: Decodable, Equatable {
|
||||
let id: String
|
||||
let label: String
|
||||
}
|
||||
|
||||
struct DesktopUserRequest: Decodable {
|
||||
let id: String
|
||||
let kind: String
|
||||
let title: String
|
||||
let message: String
|
||||
let createdAt: Date
|
||||
let expiresAt: Date
|
||||
let actions: [DesktopUserAction]
|
||||
}
|
||||
|
||||
struct DesktopAgentWindow: Decodable {
|
||||
let id: String
|
||||
let url: String
|
||||
}
|
||||
|
||||
func decodeDesktopRequests(_ data: Data) throws -> [DesktopUserRequest] {
|
||||
let decoder = JSONDecoder()
|
||||
decoder.dateDecodingStrategy = .custom { decoder in
|
||||
let value = try decoder.singleValueContainer().decode(String.self)
|
||||
let formatter = ISO8601DateFormatter()
|
||||
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
|
||||
if let date = formatter.date(from: value) { return date }
|
||||
formatter.formatOptions = [.withInternetDateTime]
|
||||
if let date = formatter.date(from: value) { return date }
|
||||
throw DecodingError.dataCorruptedError(in: try decoder.singleValueContainer(), debugDescription: "Invalid request timestamp")
|
||||
}
|
||||
return try decoder.decode([DesktopUserRequest].self, from: data)
|
||||
}
|
||||
|
||||
struct DesktopUserRequestState {
|
||||
private(set) var pending: [DesktopUserRequest] = []
|
||||
|
||||
private var announced: Set<String> = []
|
||||
|
||||
private var responding: Set<String> = []
|
||||
private var completed: Set<String> = []
|
||||
|
||||
mutating func beginResponse(baseUrl: String, requestId: String, action: String, now: Date) -> URLRequest? {
|
||||
guard !responding.contains(requestId),
|
||||
let pendingRequest = pending.first(where: { $0.id == requestId }),
|
||||
pendingRequest.expiresAt > now,
|
||||
pendingRequest.actions.contains(where: { $0.id == action }),
|
||||
let id = requestId.addingPercentEncoding(withAllowedCharacters: .alphanumerics.union(CharacterSet(charactersIn: "-._~"))),
|
||||
let url = URL(string: baseUrl.trimmingCharacters(in: CharacterSet(charactersIn: "/")) + "/desktop/requests/\(id)/respond"),
|
||||
let body = try? JSONEncoder().encode(["action": action]) else { return nil }
|
||||
var response = URLRequest(url: url)
|
||||
response.httpMethod = "POST"
|
||||
response.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
response.httpBody = body
|
||||
responding.insert(requestId)
|
||||
return response
|
||||
}
|
||||
|
||||
mutating func completeResponse(_ id: String, accepted: Bool) {
|
||||
responding.remove(id)
|
||||
if accepted { completed.insert(id); pending.removeAll { $0.id == id } }
|
||||
}
|
||||
|
||||
func isResponding(_ id: String) -> Bool { responding.contains(id) }
|
||||
|
||||
mutating func reconcile(_ requests: [DesktopUserRequest], now: Date) -> [String] {
|
||||
let previousIds = Set(pending.map(\.id))
|
||||
pending = requests.filter { $0.expiresAt > now && !completed.contains($0.id) }.sorted {
|
||||
$0.createdAt == $1.createdAt ? $0.id < $1.id : $0.createdAt < $1.createdAt
|
||||
}
|
||||
let currentIds = Set(pending.map(\.id))
|
||||
return previousIds.subtracting(currentIds).sorted()
|
||||
}
|
||||
|
||||
func notificationsToAnnounce() -> [DesktopUserRequest] {
|
||||
pending.filter { !announced.contains($0.id) && !responding.contains($0.id) }
|
||||
}
|
||||
|
||||
mutating func markAnnounced(_ id: String) {
|
||||
if pending.contains(where: { $0.id == id }) { announced.insert(id) }
|
||||
}
|
||||
}
|
||||
|
||||
extension DesktopUserRequest {
|
||||
var notificationIdentifier: String { "desktop-request." + id }
|
||||
|
||||
func notificationCategory() -> UNNotificationCategory {
|
||||
UNNotificationCategory(
|
||||
identifier: notificationIdentifier,
|
||||
actions: actions.map { UNNotificationAction(identifier: $0.id, title: $0.label, options: []) },
|
||||
intentIdentifiers: [],
|
||||
options: [.customDismissAction])
|
||||
}
|
||||
|
||||
func notificationRequest() -> UNNotificationRequest {
|
||||
let content = UNMutableNotificationContent()
|
||||
content.title = title
|
||||
content.body = message
|
||||
content.categoryIdentifier = notificationIdentifier
|
||||
content.userInfo = ["requestId": id]
|
||||
content.sound = .default
|
||||
return UNNotificationRequest(identifier: notificationIdentifier, content: content, trigger: nil)
|
||||
}
|
||||
}
|
||||
|
||||
struct DesktopAgentWindowState {
|
||||
private var opened: Set<String> = []
|
||||
|
||||
mutating func takeUnopened(_ windows: [DesktopAgentWindow]) -> [DesktopAgentWindow] {
|
||||
windows.filter { opened.insert($0.id).inserted }
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user