Skip to content

Commit

Permalink
Migrate to Vapor 4 🚀
Browse files Browse the repository at this point in the history
  • Loading branch information
MihaelIsaev committed Jan 5, 2020
1 parent c8eed24 commit 1f98a95
Show file tree
Hide file tree
Showing 14 changed files with 350 additions and 274 deletions.
8 changes: 5 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
Packages
.build
.DS_Store
/.build
/Packages
/*.xcodeproj
*.xcodeproj
DerivedData/
Package.resolved
.swiftpm
9 changes: 6 additions & 3 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
// swift-tools-version:4.0
// swift-tools-version:5.1

import PackageDescription

let package = Package(
name: "FCM",
platforms: [
.macOS(.v10_14)
],
products: [
//Vapor client for Firebase Cloud Messaging
.library(name: "FCM", targets: ["FCM"]),
],
dependencies: [
// 💧 A server-side Swift web framework.
.package(url: "https://github.com/vapor/vapor.git", from: "3.0.0"),
.package(url: "https://github.com/vapor/jwt.git", from: "3.0.0"),
.package(url: "https://github.com/vapor/vapor.git", from: "4.0.0-beta.2"),
.package(url: "https://github.com/vapor/jwt.git", from: "4.0.0-beta.2"),
],
targets: [
.target(name: "FCM", dependencies: ["Vapor", "JWT"]),
Expand Down
182 changes: 42 additions & 140 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<img src="https://img.shields.io/badge/license-MIT-brightgreen.svg" alt="MIT License">
</a>
<a href="https://swift.org">
<img src="https://img.shields.io/badge/swift-4.1-brightgreen.svg" alt="Swift 4.1">
<img src="https://img.shields.io/badge/swift-4.1-brightgreen.svg" alt="Swift 5.1">
</a>
<a href="https://twitter.com/VaporRussia">
<img src="https://img.shields.io/badge/twitter-VaporRussia-5AA9E7.svg" alt="Twitter">
Expand All @@ -19,9 +19,9 @@

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

Built for Vapor3 and depends on `JWT` Vapor lib.
Built for Vapor4 and depends on `JWT` Vapor lib.

Note: the project is in active development state and it may cause huge syntax changes before v1.0.0
Vapor3 version available in `vapor3` branch and from `1.0.0` tag.

If you have great ideas of how to improve this package write me (@iMike) in [Vapor's discord chat](http://vapor.team) or just send pull request.

Expand All @@ -33,7 +33,7 @@ Edit your `Package.swift`

```swift
//add this repo to dependencies
.package(url: "https://github.com/MihaelIsaev/FCM.git", from: "1.0.0")
.package(url: "https://github.com/MihaelIsaev/FCM.git", from: "2.0.0")
//and don't forget about targets
//"FCM"
```
Expand All @@ -45,65 +45,42 @@ First of all you should configure FCM in `configure.swift`
```swift
import FCM

/// Called before your application initializes.
public func configure(_ config: inout Config, _ env: inout Environment, _ services: inout Services) throws {
//here you should initialize 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: "...")
}
```

#### There are two ways

##### 1. Using environment variables 👍
```swift
let fcm = FCM()
services.register(fcm, as: FCM.self)
```
and don't forget to pass the following environment variables
```swift
fcmServiceAccountKeyPath // /tmp/serviceAccountKey.json
```
OR
```swift
fcmEmail // [email protected]
fcmKeyPath // /tmp/fcm.pem
fcmProjectId // example-3ab5c
```

##### 2. Manually 🤖
```swift
let fcm = FCM(pathToServiceAccountKey: "/tmp/serviceAccountKey.json")
services.register(fcm, as: FCM.self)
```
OR
```swift
let fcm = FCM(email: "[email protected]",
projectId: "example-3ab5c",
pathToKey: "/tmp/fcm.pem")
services.register(fcm, as: FCM.self)
```
OR
```swift
let fcm = FCM(email: "[email protected]",
projectId: "example-3ab5c",
key: "<YOUR PRIVATE KEY>")
services.register(fcm, as: FCM.self)
```

> ⚠️ **TIP:** `serviceAccountKey.json` you could get from [Firebase Console](https://console.firebase.google.com)
>
> 🔑 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`
Add the following code to your `configure.swift` after `app.fcm.configuration = ...`
```swift
fcm.apnsDefaultConfig = FCMApnsConfig(headers: [:],
aps: FCMApnsApsObject(sound: "default"))
fcm.androidDefaultConfig = FCMAndroidConfig(ttl: "86400s",
restricted_package_name: "com.example.myapp",
notification: FCMAndroidNotification(sound: "default"))
fcm.webpushDefaultConfig = FCMWebpushConfig(headers: [:],
data: [:],
notification: [:])
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! 🚀

Expand All @@ -112,98 +89,23 @@ Then you could send push notifications using token, topic or condition.
Here's an example route handler with push notification sending using token

```swift
router.get("testfcm") { req -> Future<String> in
let fcm = try req.make(FCM.self)
let token = "<YOUR FIREBASE DEVICE TOKEN>"
let notification = FCMNotification(title: "Vapor is awesome!", body: "Swift one love! ❤️")
let message = FCMMessage(token: token, notification: notification)
return try fcm.sendMessage(req.client(), message: message)
}
```

`fcm.sendMessage` 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.

## Bonus 🍾

In my Vapor projects I'm using this extension for sending push notifications

```swift
import Vapor
import Fluent
import FCM

protocol Firebaseable: Model {
var firebaseToken: String? { get set }
}

extension Firebaseable {
func sendPush(title: String, message: String, on req: Container) throws -> Future<Void> {
guard let token = firebaseToken else {
return req.eventLoop.newSucceededFuture(result: ())
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 try req.fcm.send(message).map { name in
return "Just sent: \(name)"
}
return try Self.sendPush(title: title, message: message, token: token, on: req)
}

static func sendPush(title: String, message: String, token: String, on container: Container) throws -> Future<Void> {
let fcm = try container.make(FCM.self)
let message = FCMMessage(token: token, notification: FCMNotification(title: title, body: message))
return try fcm.sendMessage(container.make(Client.self), message: message).transform(to: ())
}
}

extension Array where Element: Firebaseable {
func sendPush(title: String, message: String, on container: Container) throws -> Future<Void> {
return try map { try $0.sendPush(title: title, message: message, on: container) }.flatten(on: container)
}
}
```
Optionally you can handle `sendMessage` error through defining `catchFlatMap` after it, e.g. for removing broken tokens or anything else
```swift
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: ())
}
}
```

> Special thanks to @grahamburgsma for `GoogleError` and `FCMError` #10
Then e.g. I'm conforming my `Token` model to `Firebaseable`
`fcm.send` returns message name like `projects/example-3ab5c/messages/1531222329648135`

```swift
final class Token: Content {
var id: UUID?
var token: String
var userId: User.ID
var firebaseToken: String?
}
extension Token: Firebaseable {}
```
`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.

So then you'll be able to send pushes by querying tokens like this
```swift
Token.query(on: req)
.join(\User.id, to: \Token.userId)
.filter(\User.email == "[email protected]")
.all().map { tokens in
try tokens.sendPush(title: "Test push", message: "Hello world!", on: req)
}
```
> Special thanks to @grahamburgsma for `GoogleError` and `FCMError` #10
7 changes: 7 additions & 0 deletions Sources/FCM/FCM+Application.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Vapor

extension Application {
public var fcm: FCM {
.init(application: self)
}
}
7 changes: 7 additions & 0 deletions Sources/FCM/FCM+Request.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Vapor

extension Request {
public var fcm: FCM {
.init(application: application)
}
}
Loading

0 comments on commit 1f98a95

Please sign in to comment.