Skip to content

Commit 30538eb

Browse files
itcohortskylebrowning
authored andcommitted
Fixes #34 [BUG] TooManyProviderTokenUpdates (#35)
* requests will cache jwt and reuse until timeout * Resolves comments; Implements in struct instead of class * Implement token as a computed property
1 parent 899322d commit 30538eb

File tree

4 files changed

+70
-18
lines changed

4 files changed

+70
-18
lines changed
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the APNSwift open source project
4+
//
5+
// Copyright (c) 2019 the APNSwift project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of APNSwift project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import Foundation
16+
17+
public struct APNSwiftBearerToken {
18+
let configuration: APNSwiftConfiguration
19+
let timeout: TimeInterval
20+
var createdAt: TimeInterval?
21+
private var cachedToken: String?
22+
23+
public init(configuration: APNSwiftConfiguration, timeout: TimeInterval) {
24+
self.configuration = configuration
25+
self.timeout = timeout
26+
}
27+
28+
public var token: String? {
29+
mutating get {
30+
let now = Date().timeIntervalSince1970
31+
guard let existingToken = cachedToken, let timeCreated = createdAt, (now - timeCreated) <= timeout else {
32+
cachedToken = try? createToken()
33+
createdAt = now
34+
return cachedToken
35+
}
36+
return existingToken
37+
}
38+
}
39+
40+
private func createToken() throws -> String {
41+
let jwt = APNSwiftJWT(keyID: configuration.keyIdentifier, teamID: configuration.teamIdentifier, issueDate: Date(), expireDuration: timeout)
42+
var token: String
43+
let digestValues = try jwt.getDigest()
44+
var signature = try configuration.signer.sign(digest: digestValues.fixedDigest)
45+
guard let data = signature.readData(length: signature.readableBytes) else {
46+
throw APNSwiftError.SigningError.invalidSignatureData
47+
}
48+
token = digestValues.digest + "." + data.base64EncodedURLString()
49+
return token
50+
}
51+
}

Sources/APNSwift/APNSwiftConnection.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ public final class APNSwiftConnection {
7676
/**
7777
APNSwiftConnection send method. Sends a notification to the desired deviceToken.
7878
- Parameter notification: the notification meta data and alert to send.
79+
- Parameter bearerToken: the bearer token to authenitcate our request
7980
- Parameter deviceToken: device token to send alert to.
8081
- Parameter encoder: customer JSON encoder if needed.
8182
- Parameter expiration: a date that the notificaiton expires.
@@ -89,16 +90,17 @@ public final class APNSwiftConnection {
8990
```
9091
let apns = APNSwiftConnection.connect()
9192
let expiry = Date().addingTimeInterval(5)
92-
try apns.send(notification, to: "b27a07be2092c7fbb02ab5f62f3135c615e18acc0ddf39a30ffde34d41665276", with: JSONEncoder(), expiration: expiry, priority: 10, collapseIdentifier: "huro2").wait()
93+
let bearerToken = APNSwiftBearerToken(configuration: apnsConfig, timeout: 50.0)
94+
try apns.send(notification, bearerToken: bearerToken,to: "b27a07be2092c7fbb02ab5f62f3135c615e18acc0ddf39a30ffde34d41665276", with: JSONEncoder(), expiration: expiry, priority: 10, collapseIdentifier: "huro2").wait()
9395
```
9496
*/
95-
public func send<Notification>(_ notification: Notification, to deviceToken: String, with encoder: JSONEncoder = JSONEncoder(), expiration: Date? = nil, priority: Int? = nil, collapseIdentifier: String? = nil, topic: String? = nil) -> EventLoopFuture<Void>
97+
public func send<Notification>(_ notification: Notification, bearerToken: APNSwiftBearerToken, to deviceToken: String, with encoder: JSONEncoder = JSONEncoder(), expiration: Date? = nil, priority: Int? = nil, collapseIdentifier: String? = nil, topic: String? = nil) -> EventLoopFuture<Void>
9698
where Notification: APNSwiftNotification {
9799
let streamPromise = channel.eventLoop.makePromise(of: Channel.self)
98100
multiplexer.createStreamChannel(promise: streamPromise) { channel, streamID in
99101
let handlers: [ChannelHandler] = [
100102
HTTP2ToHTTP1ClientCodec(streamID: streamID, httpProtocol: .https),
101-
APNSwiftRequestEncoder<Notification>(deviceToken: deviceToken, configuration: self.configuration, expiration: expiration, priority: priority, collapseIdentifier: collapseIdentifier),
103+
APNSwiftRequestEncoder<Notification>(deviceToken: deviceToken, configuration: self.configuration, bearerToken: bearerToken, expiration: expiration, priority: priority, collapseIdentifier: collapseIdentifier),
102104
APNSwiftResponseDecoder(),
103105
APNSwiftStreamHandler(),
104106
]

Sources/APNSwift/APNSwiftRequestEncoder.swift

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,17 @@ internal final class APNSwiftRequestEncoder<Notification>: ChannelOutboundHandle
2727
typealias OutboundOut = HTTPClientRequestPart
2828

2929
let configuration: APNSwiftConfiguration
30+
var bearerToken: APNSwiftBearerToken
3031
let deviceToken: String
3132
let priority: Int?
3233
let expiration: Date?
3334
let collapseIdentifier: String?
3435
let topic: String?
35-
36-
init(deviceToken: String, configuration: APNSwiftConfiguration, expiration: Date?, priority: Int?, collapseIdentifier: String?, topic: String? = nil) {
36+
37+
38+
init(deviceToken: String, configuration: APNSwiftConfiguration, bearerToken: APNSwiftBearerToken, expiration: Date?, priority: Int?, collapseIdentifier: String?, topic: String? = nil) {
3739
self.configuration = configuration
40+
self.bearerToken = bearerToken
3841
self.deviceToken = deviceToken
3942
self.expiration = expiration
4043
self.priority = priority
@@ -65,18 +68,8 @@ internal final class APNSwiftRequestEncoder<Notification>: ChannelOutboundHandle
6568
reqHead.headers.add(name: "apns-collapse-id", value: collapseId)
6669
}
6770
reqHead.headers.add(name: "host", value: configuration.url.host!)
68-
let jwt = APNSwiftJWT(keyID: configuration.keyIdentifier, teamID: configuration.teamIdentifier, issueDate: Date(), expireDuration: 60 * 60)
69-
var token: String
70-
do {
71-
let digestValues = try jwt.getDigest()
72-
let signature = try configuration.signer.sign(digest: digestValues.fixedDigest)
73-
guard let data = signature.getData(at: 0, length: signature.readableBytes) else {
74-
throw APNSwiftError.SigningError.invalidSignatureData
75-
}
76-
token = digestValues.digest + "." + data.base64EncodedURLString()
77-
} catch {
78-
promise?.fail(error)
79-
context.close(promise: nil)
71+
guard let token = bearerToken.token else {
72+
promise?.fail(APNSwiftError.SigningError.invalidSignatureData)
8073
return
8174
}
8275
reqHead.headers.add(name: "authorization", value: "bearer \(token)")

Sources/APNSwiftExample/main.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,16 @@ let aps = APNSwiftPayload(alert: alert, badge: 0, sound: .critical(apsSound), ha
5252
let temp = try! JSONEncoder().encode(aps)
5353
let string = String(bytes: temp, encoding: .utf8)
5454
let notification = AcmeNotification(acme2: ["bang", "whiz"], aps: aps)
55+
let token = APNSwiftBearerToken(configuration: apnsConfig, timeout: 50.0)
5556

5657
do {
5758
let expiry = Date().addingTimeInterval(5)
58-
try apns.send(notification, to: "b693f99efa926bcf9977adae75bc88a97988365f49e4c6abf94aa659a4d87a6b", expiration: expiry, priority: 10).wait()
59+
try apns.send(notification, bearerToken: token, to: "b693f99efa926bcf9977adae75bc88a97988365f49e4c6abf94aa659a4d87a6b", expiration: expiry, priority: 10).wait()
60+
try apns.send(notification, bearerToken: token, to: "b693f99efa926bcf9977adae75bc88a97988365f49e4c6abf94aa659a4d87a6b", expiration: expiry, priority: 10).wait()
61+
try apns.send(notification, bearerToken: token, to: "b693f99efa926bcf9977adae75bc88a97988365f49e4c6abf94aa659a4d87a6b", expiration: expiry, priority: 10).wait()
62+
try apns.send(notification, bearerToken: token, to: "b693f99efa926bcf9977adae75bc88a97988365f49e4c6abf94aa659a4d87a6b", expiration: expiry, priority: 10).wait()
63+
try apns.send(notification, bearerToken: token, to: "b693f99efa926bcf9977adae75bc88a97988365f49e4c6abf94aa659a4d87a6b", expiration: expiry, priority: 10).wait()
64+
try apns.send(notification, bearerToken: token, to: "b693f99efa926bcf9977adae75bc88a97988365f49e4c6abf94aa659a4d87a6b", expiration: expiry, priority: 10).wait()
5965
} catch {
6066
print(error)
6167
}

0 commit comments

Comments
 (0)