import Foundation import APNSwift import NIO import Crypto struct APNSInterface { private struct Payload: Codable {} private var apnsConfiguration: APNSClientConfiguration! private var apnsNotification: APNSAlertNotification! private let apnsEventGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) private let apnsRequestEncoder = JSONEncoder() private let apnsResponseDecoder = JSONDecoder() init?(_ config: ServerConfiguration) { guard let key = load(keyAtPath: config.privateKeyFilePath) else { return nil } let authentication = APNSClientConfiguration.AuthenticationMethod.jwt( privateKey: key, keyIdentifier: config.keyIdentifier, teamIdentifier: config.teamIdentifier) apnsConfiguration = APNSClientConfiguration( authenticationMethod: authentication, environment: .sandbox) let alert = APNSAlertNotificationContent( title: .raw(config.messageTitle), body: .raw(config.messageBody)) apnsNotification = APNSAlertNotification( alert: alert, expiration: .none, priority: .immediately, topic: config.topic, payload: Payload(), sound: .default) } func sendPush(to tokens: Set) async { let client = APNSClient( configuration: apnsConfiguration, eventLoopGroupProvider: .shared(apnsEventGroup), responseDecoder: apnsResponseDecoder, requestEncoder: apnsRequestEncoder) log(info: "Client created") do { for token in tokenStorage.tokens { log(info: "Sending push to \(token.prefix(6))...") try await client.sendAlertNotification( apnsNotification, deviceToken: token, deadline: .now() + .seconds(10)) log(info: "Sent push to \(token.prefix(6))...") } } catch let error as APNSError { print(error) } catch { log(error: error.localizedDescription) } do { log("Closing client") try client.syncShutdown() } catch { log(error: error.localizedDescription) } } } private func load(keyAtPath path: String) -> P256.Signing.PrivateKey? { do { guard let key = try P256.Signing.PrivateKey.loadFrom(filePath: path) else { print("Failed to read key \(path)") return nil } return key } catch { print("Failed to load key \(error)") return nil } }