-
Notifications
You must be signed in to change notification settings - Fork 39
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
🔥 Implement pure APNS to Firebase token converter
So now in your iOS app you can throw away Firebase libs from dependencies cause you can send pure APNS token to your server app and it will register it in Firebase by itself. It is must have for developers who don't want to add Firebase libs into their apps, and especially for iOS projects who use Swift Package Manager cause Firebase doesn't have SPM support for its libs yet.
- Loading branch information
1 parent
859deee
commit b29c4d2
Showing
3 changed files
with
149 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
import Foundation | ||
import Vapor | ||
|
||
public struct RegisterAPNSID { | ||
let appBundleId: String | ||
let serverKey: String? | ||
let sandbox: Bool | ||
|
||
public init (appBundleId: String, serverKey: String? = nil, sandbox: Bool = false) { | ||
self.appBundleId = appBundleId | ||
self.serverKey = serverKey | ||
self.sandbox = sandbox | ||
} | ||
} | ||
|
||
extension RegisterAPNSID { | ||
public static var env: RegisterAPNSID { | ||
guard let appBundleId = Environment.get("FCM_APP_BUNDLE_ID") else { | ||
fatalError("FCM: Register APNS: missing FCM_APP_BUNDLE_ID environment variable") | ||
} | ||
return .init(appBundleId: appBundleId) | ||
} | ||
} | ||
|
||
public struct APNSToFirebaseToken { | ||
public let registration_token, apns_token: String | ||
public let isRegistered: Bool | ||
} | ||
|
||
extension FCM { | ||
/// Helper method which registers your pure APNS token in Firebase Cloud Messaging | ||
/// and returns firebase tokens for each APNS token | ||
/// | ||
/// Convenient way | ||
/// | ||
/// Declare `RegisterAPNSID` via extension | ||
/// ```swift | ||
/// extension RegisterAPNSID { | ||
/// static var myApp: RegisterAPNSID { .init(appBundleId: "com.myapp") } | ||
/// } | ||
/// ``` | ||
/// | ||
public func registerAPNS( | ||
_ id: RegisterAPNSID, | ||
tokens: String..., | ||
on eventLoop: EventLoop? = nil) -> EventLoopFuture<[APNSToFirebaseToken]> { | ||
registerAPNS(appBundleId: id.appBundleId, serverKey: id.serverKey, sandbox: id.sandbox, tokens: tokens, on: eventLoop) | ||
} | ||
|
||
/// Helper method which registers your pure APNS token in Firebase Cloud Messaging | ||
/// and returns firebase tokens for each APNS token | ||
/// | ||
/// Convenient way | ||
/// | ||
/// Declare `RegisterAPNSID` via extension | ||
/// ```swift | ||
/// extension RegisterAPNSID { | ||
/// static var myApp: RegisterAPNSID { .init(appBundleId: "com.myapp") } | ||
/// } | ||
/// ``` | ||
/// | ||
public func registerAPNS( | ||
_ id: RegisterAPNSID, | ||
tokens: [String], | ||
on eventLoop: EventLoop? = nil) -> EventLoopFuture<[APNSToFirebaseToken]> { | ||
registerAPNS(appBundleId: id.appBundleId, serverKey: id.serverKey, sandbox: id.sandbox, tokens: tokens, on: eventLoop) | ||
} | ||
|
||
/// Helper method which registers your pure APNS token in Firebase Cloud Messaging | ||
/// and returns firebase tokens for each APNS token | ||
public func registerAPNS( | ||
appBundleId: String, | ||
serverKey: String? = nil, | ||
sandbox: Bool = false, | ||
tokens: String..., | ||
on eventLoop: EventLoop? = nil) -> EventLoopFuture<[APNSToFirebaseToken]> { | ||
registerAPNS(appBundleId: appBundleId, serverKey: serverKey, sandbox: sandbox, tokens: tokens, on: eventLoop) | ||
} | ||
|
||
/// Helper method which registers your pure APNS token in Firebase Cloud Messaging | ||
/// and returns firebase tokens for each APNS token | ||
public func registerAPNS( | ||
appBundleId: String, | ||
serverKey: String? = nil, | ||
sandbox: Bool = false, | ||
tokens: [String], | ||
on eventLoop: EventLoop? = nil) -> EventLoopFuture<[APNSToFirebaseToken]> { | ||
let eventLoop = eventLoop ?? application.eventLoopGroup.next() | ||
guard tokens.count <= 100 else { | ||
return eventLoop.makeFailedFuture(Abort(.internalServerError, reason: "FCM: Register APNS: tokens count should be less or equeal 100")) | ||
} | ||
guard tokens.count > 0 else { | ||
return eventLoop.future([]) | ||
} | ||
guard let configuration = self.configuration else { | ||
#if DEBUG | ||
fatalError("FCM not configured. Use app.fcm.configuration = ...") | ||
#else | ||
return eventLoop.future([]) | ||
#endif | ||
} | ||
guard let serverKey = serverKey ?? configuration.serverKey else { | ||
fatalError("FCM: Register APNS: Server Key is missing.") | ||
} | ||
let url = iidURL + "batchImport" | ||
return eventLoop.future().flatMapThrowing { accessToken throws -> HTTPClient.Request in | ||
struct Payload: Codable { | ||
let application: String | ||
let sandbox: Bool | ||
let apns_tokens: [String] | ||
} | ||
let payload = Payload(application: appBundleId, sandbox: false, apns_tokens: tokens) | ||
let payloadData = try JSONEncoder().encode(payload) | ||
|
||
var headers = HTTPHeaders() | ||
headers.add(name: "Authorization", value: "key=\(serverKey)") | ||
headers.add(name: "Content-Type", value: "application/json") | ||
|
||
return try .init(url: url, method: .POST, headers: headers, body: .data(payloadData)) | ||
}.flatMap { request in | ||
return self.client.execute(request: request).flatMapThrowing { res in | ||
guard 200 ..< 300 ~= res.status.code else { | ||
guard | ||
let bb = res.body, | ||
let bytes = bb.getBytes(at: 0, length: bb.readableBytes), | ||
let reason = String(bytes: bytes, encoding: .utf8) else { | ||
throw Abort(.internalServerError, reason: "FCM: Register APNS: unable to decode error response") | ||
} | ||
throw Abort(.internalServerError, reason: reason) | ||
} | ||
struct Result: Codable { | ||
struct Result: Codable { | ||
let registration_token, apns_token, status: String | ||
} | ||
var results: [Result] | ||
} | ||
guard let body = res.body, let result = try? JSONDecoder().decode(Result.self, from: body) else { | ||
throw Abort(.notFound, reason: "FCM: Register APNS: empty response") | ||
} | ||
return result.results.map { | ||
.init(registration_token: $0.registration_token, apns_token: $0.apns_token, isRegistered: $0.status == "OK") | ||
} | ||
} | ||
} | ||
} | ||
} |