Compare commits
5 Commits
f2f802a24f
...
master
Author | SHA1 | Date | |
---|---|---|---|
ec309e77db | |||
c19bc3c299 | |||
5cc7782d4e | |||
2f0827a430 | |||
34edc88611 |
89
Sources/App/APNSInterface.swift
Normal file
89
Sources/App/APNSInterface.swift
Normal file
@ -0,0 +1,89 @@
|
||||
import Foundation
|
||||
import APNSwift
|
||||
import NIO
|
||||
import Crypto
|
||||
|
||||
struct APNSInterface {
|
||||
|
||||
private struct Payload: Codable {}
|
||||
|
||||
private var apnsConfiguration: APNSClientConfiguration!
|
||||
private let title: String
|
||||
private let messages: [String]
|
||||
private let topic: String
|
||||
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)
|
||||
title = config.messageTitle
|
||||
messages = config.messages
|
||||
topic = config.topic
|
||||
}
|
||||
|
||||
func sendPush(to tokens: Set<String>) async {
|
||||
let client = APNSClient(
|
||||
configuration: apnsConfiguration,
|
||||
eventLoopGroupProvider: .shared(apnsEventGroup),
|
||||
responseDecoder: apnsResponseDecoder,
|
||||
requestEncoder: apnsRequestEncoder)
|
||||
log(info: "Client created")
|
||||
let alert = APNSAlertNotificationContent(
|
||||
title: .raw(title),
|
||||
body: .raw(messages.randomElement()!))
|
||||
|
||||
let apnsNotification = APNSAlertNotification(
|
||||
alert: alert,
|
||||
expiration: .none,
|
||||
priority: .immediately,
|
||||
topic: topic,
|
||||
payload: Payload(),
|
||||
sound: .default)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ struct ServerConfiguration: Codable {
|
||||
|
||||
let messageTitle: String
|
||||
|
||||
let messageBody: String
|
||||
let messages: [String]
|
||||
|
||||
let buttonPin: Int
|
||||
|
||||
|
@ -18,11 +18,13 @@ struct TokenStorage {
|
||||
|
||||
private mutating func loadTokensFromDisk() {
|
||||
guard FileManager.default.fileExists(atPath: fileUrl.path) else {
|
||||
log(info: "No tokens loaded")
|
||||
return
|
||||
}
|
||||
do {
|
||||
let data = try Data(contentsOf: fileUrl)
|
||||
tokens = try BinaryDecoder().decode(from: data)
|
||||
log(info: "\(tokens.count) tokens loaded")
|
||||
} catch {
|
||||
log(error: "Failed to read token file: \(error)")
|
||||
}
|
||||
|
@ -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<Payload>!
|
||||
private let apnsEventGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)
|
||||
private let apnsRequestEncoder = JSONEncoder()
|
||||
private let apnsResponseDecoder = JSONDecoder()
|
||||
|
||||
var apns: APNSInterface?
|
||||
var tokenStorage: TokenStorage!
|
||||
|
||||
// configures your application
|
||||
@ -20,8 +10,6 @@ public func configure(_ app: Application) throws {
|
||||
let resourcesFolderUrl = URL(fileURLWithPath: app.directory.resourcesDirectory)
|
||||
let configUrl = resourcesFolderUrl.appendingPathComponent("config.json")
|
||||
|
||||
tokenStorage = .init(in: resourcesFolderUrl)
|
||||
|
||||
let config: ServerConfiguration
|
||||
do {
|
||||
let data = try Data(contentsOf: configUrl)
|
||||
@ -30,10 +18,14 @@ public func configure(_ app: Application) throws {
|
||||
print("Failed to load config from \(configUrl.path): \(error)")
|
||||
return
|
||||
}
|
||||
Log.set(logFile: URL(fileURLWithPath: config.logPath))
|
||||
|
||||
tokenStorage = .init(in: resourcesFolderUrl)
|
||||
|
||||
app.http.server.configuration.port = config.port
|
||||
|
||||
guard configureAPNS(config) else {
|
||||
apns = .init(config)
|
||||
guard apns != nil else {
|
||||
serverStatus = .failedToStart
|
||||
return
|
||||
}
|
||||
@ -44,6 +36,7 @@ public func configure(_ app: Application) throws {
|
||||
try routes(app)
|
||||
|
||||
serverStatus = .running
|
||||
log(info: "Server is running")
|
||||
}
|
||||
|
||||
private extension JSONDecoder {
|
||||
@ -53,47 +46,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)",
|
||||
@ -101,7 +53,7 @@ private func configureGPIO(_ config: ServerConfiguration) {
|
||||
baseAddr: 0x7E000000)
|
||||
|
||||
gpio.direction = .IN
|
||||
gpio.pull = .down
|
||||
gpio.pull = .neither
|
||||
gpio.bounceTime = config.bounceTime
|
||||
gpio.onChange { _ in
|
||||
log(info: "Push detected")
|
||||
@ -116,31 +68,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)
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user