-
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.
- Loading branch information
1 parent
c8eed24
commit 1f98a95
Showing
14 changed files
with
350 additions
and
274 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
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 |
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 |
---|---|---|
|
@@ -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"> | ||
|
@@ -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. | ||
|
||
|
@@ -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" | ||
``` | ||
|
@@ -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! 🚀 | ||
|
||
|
@@ -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 |
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,7 @@ | ||
import Vapor | ||
|
||
extension Application { | ||
public var fcm: FCM { | ||
.init(application: self) | ||
} | ||
} |
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,7 @@ | ||
import Vapor | ||
|
||
extension Request { | ||
public var fcm: FCM { | ||
.init(application: application) | ||
} | ||
} |
Oops, something went wrong.