Swiftpack.co - Package - MihaelIsaev/FCM

Mihael Isaev

MIT License Swift 5.1 Twitter


Intro 👏

It's a swift lib that gives ability to send push notifications through Firebase Cloud Messaging.

Built for Vapor4 and depends on JWT Vapor lib.

💡Vapor3 version is available in vapor3 branch and from 1.0.0 tag

If you have great ideas of how to improve this package write me (@iMike#3049) in Vapor's discord chat or just send pull request.

Hope it'll be useful for someone :)

Install through Swift Package Manager ❤️

Edit your Package.swift

//add this repo to dependencies
.package(url: "https://github.com/MihaelIsaev/FCM.git", from: "2.1.0")
//and don't forget about targets
//"FCM"

How it works ?

First of all you should configure FCM in configure.swift

import FCM

// Called before your application initializes.
func configure(_ app: Application) throws {
    /// case 1
    /// with service account json file
    /// put into your environment variables the following key:
    /// FCM_SERVICE_ACCOUNT_KEY_PATH=path/to/serviceAccountKey.json
    app.fcm.configuration = .envServiceAccountKey

    /// case 2
    /// put into your environment variables the following keys:
    /// FCM_EMAIL=...
    /// FCM_PROJECT_ID=...
    /// FCM_KEY_PATH=path/to/key.pem
    app.fcm.configuration = .envCredentials

    /// case 3
    /// manually
    app.fcm.configuration = .init(email: "...", projectId: "...", key: "...")
}

⚠️ TIP: serviceAccountKey.json you could get from Firebase Console

🔑 Just go to Settings -> Service Accounts tab and press Create Private Key button in e.g. NodeJS tab

OPTIONAL: Set default configurations, e.g. to enable notification sound

Add the following code to your configure.swift after app.fcm.configuration = ...

app.fcm.apnsDefaultConfig = FCMApnsConfig(headers: [:],
                                          aps: FCMApnsApsObject(sound: "default"))
app.fcm.androidDefaultConfig = FCMAndroidConfig(ttl: "86400s",
                                                restricted_package_name: "com.example.myapp",
                                                notification: FCMAndroidNotification(sound: "default"))
app.fcm.webpushDefaultConfig = FCMWebpushConfig(headers: [:],
                                                data: [:],
                                                notification: [:])

Let's send first push notification! 🚀

Then you could send push notifications using token, topic or condition.

Here's an example route handler with push notification sending using token

import FCM

func routes(_ app: Application) throws {
    app.get("testfcm") { req -> EventLoopFuture<String> in
        let token = "<YOUR FIREBASE DEVICE TOKEN>" // get it from iOS/Android SDK
        let notification = FCMNotification(title: "Vapor is awesome!", body: "Swift one love! ❤️")
        let message = FCMMessage(token: token, notification: notification)
        return req.fcm.send(message).map { name in
            return "Just sent: \(name)"
        }
    }
}

fcm.send returns message name like projects/example-3ab5c/messages/1531222329648135

FCMMessage struct is absolutely the same as Message struct in Firebase docs https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages So you could take a look on its source code to build proper message.

Special thanks to @grahamburgsma for GoogleError and FCMError #10

Github

link
Stars: 51

Dependencies

Used By

Total: 0

Releases

New properties for `FCMAndroidNotification` (#18) - 2020-01-05 23:09:51

Thanks to @nerzh ❤️

New properties for `FCMAndroidNotification` (#18) - 2020-01-05 23:09:28

Thanks to @nerzh ❤️

Vapor 4 🚀 - 2020-01-05 18:25:54

Please read the readme to get all the details

Add `sendMessage` to the FCMProvider protocol - 2019-11-27 15:25:27

Add sendMessage to the FCMProvider protocol so it can be mocked in tests (#13)

Added FCMError to make handling specific errors easier - 2019-03-11 22:58:52

Many thanks to @grahamburgsma

So, now we can parse Google and FCM errors, let's take a look how

return try fcm.sendMessage(container.make(Client.self), message: message).transform(to: ()).catchFlatMap { error in
    guard let googleError = error as? GoogleError, let fcmError = googleError.fcmError else {
        return container.eventLoop.newFailedFuture(error: error)
    }
    switch fcmError.errorCode {
    case .unspecified: break
    case .invalid: break
    case .unregistered: break
    case .senderIDMismatch: break
    case .quotaExceeded: break
    case .apnsAuth: break
    case .unavailable: break
    case .internal: break
    }
}

And now we can delete token if we're getting unregistered error like this:

return try fcm.sendMessage(container.make(Client.self), message: message).transform(to: ()).catchFlatMap { error in
    guard let googleError = error as? GoogleError, let fcmError = googleError.fcmError else {
        return container.eventLoop.newSucceededFuture(result: ())
    }
    switch fcmError.errorCode {
        case .unregistered: // drop token only if unregistered
            return container.requestPooledConnection(to: .psql).flatMap { conn in
                return Self.query(on: conn).filter(\.firebaseToken == token).first().flatMap { model in
                    defer { try? container.releasePooledConnection(conn, to: .psql) }
                    guard var model = model else { return container.eventLoop.newSucceededFuture(result: ()) }
                    model.firebaseToken = nil
                    return model.save(on: conn).transform(to: ())
                }.always {
                    try? container.releasePooledConnection(conn, to: .psql)
                }
            }
        default:
            return container.eventLoop.newSucceededFuture(result: ())
    }
}

Throw an error if unable to get access token - 2018-10-02 08:21:18

Fixing access token refreshing - 2018-10-02 08:07:25

This is fixing unexpected behavior when the server is unable to send pushes after an hour after launch.