2022-09-04 15:01:35 +02:00
|
|
|
import Vapor
|
|
|
|
import SwiftyGPIO
|
|
|
|
import APNSwift
|
|
|
|
import NIO
|
|
|
|
import Crypto
|
|
|
|
|
|
|
|
private struct Payload: Codable {}
|
|
|
|
|
|
|
|
private var apnsConfiguration: APNSClientConfiguration!
|
|
|
|
private var apnsNotification: APNSAlertNotification<Payload>!
|
|
|
|
private let apnsEventGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)
|
|
|
|
private let apnsRequestEncoder = JSONEncoder()
|
|
|
|
private let apnsResponseDecoder = JSONDecoder()
|
|
|
|
|
|
|
|
// configures your application
|
|
|
|
public func configure(_ app: Application) throws {
|
|
|
|
|
|
|
|
let resourcesFolderUrl = URL(fileURLWithPath: app.directory.resourcesDirectory)
|
|
|
|
let configUrl = resourcesFolderUrl.appendingPathComponent("config.json")
|
|
|
|
|
|
|
|
let config: ServerConfiguration
|
|
|
|
do {
|
|
|
|
let data = try Data(contentsOf: configUrl)
|
|
|
|
config = try JSONDecoder().decode(from: data)
|
|
|
|
} catch {
|
|
|
|
print("Failed to load config: \(error)")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-09-04 15:40:52 +02:00
|
|
|
app.http.server.configuration.port = config.port
|
|
|
|
|
2022-09-04 15:01:35 +02:00
|
|
|
guard configureAPNS(config) else {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
configureGPIO(config)
|
|
|
|
|
|
|
|
// register routes
|
|
|
|
try routes(app)
|
|
|
|
|
|
|
|
serverStatus = .running
|
|
|
|
}
|
|
|
|
|
|
|
|
private extension JSONDecoder {
|
|
|
|
|
|
|
|
func decode<T>(from data: Data) throws -> T where T: Decodable {
|
|
|
|
try self.decode(T.self, from: data)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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)",
|
|
|
|
id: config.buttonPin,
|
|
|
|
baseAddr: 0x7E000000)
|
|
|
|
|
|
|
|
gpio.direction = .IN
|
|
|
|
gpio.pull = .down
|
|
|
|
gpio.bounceTime = config.bounceTime
|
|
|
|
gpio.onChange { _ in
|
|
|
|
log(info: "Push detected")
|
|
|
|
sendPush()
|
|
|
|
}
|
|
|
|
log(info: "GPIO \(config.buttonPin) configured")
|
|
|
|
}
|
|
|
|
|
|
|
|
private func sendPush() {
|
|
|
|
Task(priority: .userInitiated) {
|
|
|
|
let client = APNSClient(
|
|
|
|
configuration: apnsConfiguration,
|
|
|
|
eventLoopGroupProvider: .shared(apnsEventGroup),
|
|
|
|
responseDecoder: apnsResponseDecoder,
|
|
|
|
requestEncoder: apnsRequestEncoder)
|
|
|
|
log(info: "Client created")
|
|
|
|
do {
|
|
|
|
for token in knownTokens {
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|