APNSJWTClaims.swift 2.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980
  1. import Foundation
  2. import SwiftJWT
  3. struct APNSJWTClaims: Claims {
  4. let iss: String
  5. let iat: Date
  6. }
  7. class APNSJWTManager {
  8. static let shared = APNSJWTManager()
  9. private init() {}
  10. private struct JWTCacheKey: Hashable {
  11. let keyId: String
  12. let teamId: String
  13. }
  14. private struct CachedJWT {
  15. let token: String
  16. let expirationDate: Date
  17. }
  18. // Cache multiple JWTs for different LoopFollow instances
  19. private var jwtCache: [JWTCacheKey: CachedJWT] = [:]
  20. private let cacheQueue = DispatchQueue(label: "com.trio.apnsjwtmanager.cache", attributes: .concurrent)
  21. func getOrGenerateJWT(keyId: String, teamId: String, apnsKey: String) -> String? {
  22. let cacheKey = JWTCacheKey(keyId: keyId, teamId: teamId)
  23. // Check cache first
  24. if let cachedJWT = getCachedJWT(for: cacheKey) {
  25. return cachedJWT
  26. }
  27. // Generate new JWT
  28. let header = Header(kid: keyId)
  29. let claims = APNSJWTClaims(iss: teamId, iat: Date())
  30. var jwt = JWT(header: header, claims: claims)
  31. do {
  32. let privateKey = Data(apnsKey.utf8)
  33. let jwtSigner = JWTSigner.es256(privateKey: privateKey)
  34. let signedJWT = try jwt.sign(using: jwtSigner)
  35. // Cache the JWT with 55 minute expiration (5 minute buffer before 1 hour)
  36. let expirationDate = Date().addingTimeInterval(3300)
  37. cacheJWT(signedJWT, for: cacheKey, expirationDate: expirationDate)
  38. return signedJWT
  39. } catch {
  40. debug(.remoteControl, "Failed to sign JWT: \(error.localizedDescription)")
  41. return nil
  42. }
  43. }
  44. private func getCachedJWT(for key: JWTCacheKey) -> String? {
  45. cacheQueue.sync {
  46. guard let cached = jwtCache[key],
  47. Date() < cached.expirationDate
  48. else {
  49. return nil
  50. }
  51. return cached.token
  52. }
  53. }
  54. private func cacheJWT(_ token: String, for key: JWTCacheKey, expirationDate: Date) {
  55. cacheQueue.async(flags: .barrier) {
  56. self.jwtCache[key] = CachedJWT(token: token, expirationDate: expirationDate)
  57. }
  58. }
  59. func invalidateCache() {
  60. cacheQueue.async(flags: .barrier) {
  61. self.jwtCache.removeAll()
  62. }
  63. }
  64. }