diff --git a/Sources/App/APNSInterface.swift b/Sources/App/APNSInterface.swift new file mode 100644 index 0000000..ce98c25 --- /dev/null +++ b/Sources/App/APNSInterface.swift @@ -0,0 +1,85 @@ +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 + } +} + diff --git a/Sources/App/configure.swift b/Sources/App/configure.swift index 664d05b..be848c4 100644 --- a/Sources/App/configure.swift +++ b/Sources/App/configure.swift @@ -1,17 +1,7 @@ import Vapor import SwiftyGPIO -import APNSwift -import NIO -import Crypto - -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() +var apns: APNSInterface? var tokenStorage: TokenStorage! // configures your application @@ -33,7 +23,8 @@ public func configure(_ app: Application) throws { app.http.server.configuration.port = config.port - guard configureAPNS(config) else { + apns = .init(config) + guard apns != nil else { serverStatus = .failedToStart return } @@ -53,47 +44,6 @@ private extension JSONDecoder { } } -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 - } -} - -private func configureAPNS(_ config: ServerConfiguration) -> Bool { - guard let key = load(keyAtPath: config.privateKeyFilePath) else { - return false - } - 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) - return true -} - private func configureGPIO(_ config: ServerConfiguration) { let gpio = RaspberryGPIO( name: "GPIO\(config.buttonPin)", @@ -116,31 +66,6 @@ private func sendPush() { return } Task(priority: .userInitiated) { - 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) - } + await apns?.sendPush(to: tokenStorage.tokens) } }