Skip to content

Commit

Permalink
🔥 Implement pure APNS to Firebase token converter
Browse files Browse the repository at this point in the history
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
MihaelIsaev committed May 15, 2020
1 parent 859deee commit b29c4d2
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 0 deletions.
1 change: 1 addition & 0 deletions Sources/FCM/FCM.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public struct FCM {
let scope = "https://www.googleapis.com/auth/cloud-platform"
let audience = "https://www.googleapis.com/oauth2/v4/token"
let actionsBaseURL = "https://fcm.googleapis.com/v1/projects/"
let iidURL = "https://iid.googleapis.com/iid/v1:"

// MARK: Default configurations

Expand Down
2 changes: 2 additions & 0 deletions Sources/FCM/FCMConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import Vapor
public struct FCMConfiguration {
let email, projectId, key: String

let serverKey = Environment.get("FCM_SERVER_KEY")

// MARK: Default configurations

public var apnsDefaultConfig: FCMApnsConfig<FCMApnsPayload>?
Expand Down
146 changes: 146 additions & 0 deletions Sources/FCM/Helpers/FCM+RegisterAPNS.swift
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")
}
}
}
}
}

0 comments on commit b29c4d2

Please sign in to comment.