forked from open-runtimes/examples
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added dart example of push notification function
- Loading branch information
1 parent
988909f
commit 2ace6b7
Showing
7 changed files
with
303 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
# Send push notification | ||
|
||
A Dart Cloud Function that send push notification using Firebase Cloud Messaging to particular user | ||
|
||
* First of all make sure that you are integrated FCM into your flutter app | ||
- https://firebase.flutter.dev/docs/messaging/overview/ | ||
* Generate needed `FIREBASE_AUTH_KEY` env variable | ||
|
||
### Hot to get FCM Server Key | ||
* Go to Messaging settings | ||
![FCM Settings](firebase_messaging_setting.png "Firebase Messaging Settings") | ||
* Copy server key or create a new one | ||
![FCM Server Auth Key](firebase_auth_server_key.png "Firebase Server Auth Key") | ||
|
||
_Example input 1:_ | ||
|
||
```json | ||
{ | ||
"user_token": "some_valid_token" | ||
} | ||
``` | ||
|
||
_Example output 1:_ | ||
|
||
```json | ||
{ | ||
"success": true, | ||
"message": "Push notification successfully sent" | ||
} | ||
``` | ||
|
||
_Example input 2:_ | ||
|
||
```json | ||
{ | ||
"user_token": "" | ||
} | ||
``` | ||
|
||
_Example output 2:_ | ||
|
||
```json | ||
{ | ||
"success": false, | ||
"message": "Payload has incorrect data, user_token is empty" | ||
} | ||
``` | ||
|
||
## 📝 Environment Variables | ||
|
||
List of environment variables used by this cloud function: | ||
|
||
* **FIREBASE_AUTH_KEY** - API Key for FCM | ||
|
||
<!-- * **TINYURL_API_KEY** - API Key for TinyUrl --> | ||
|
||
## 🚀 Deployment | ||
|
||
1. Clone this repository, and enter this function folder: | ||
|
||
```shell | ||
git clone https://github.com/open-runtimes/examples.git && cd examples | ||
cd dart/send_push_notification | ||
``` | ||
|
||
2. Enter this function folder and build the code: | ||
|
||
```shell | ||
docker run -e INTERNAL_RUNTIME_ENTRYPOINT=lib/main.dart --rm --interactive --tty --volume $PWD:/usr/code openruntimes/dart:v2-2.17 sh /usr/local/src/build.sh | ||
``` | ||
|
||
As a result, a `code.tar.gz` file will be generated. | ||
|
||
3. Start the Open Runtime: | ||
|
||
```shell | ||
docker run -p 3000:3000 -e INTERNAL_RUNTIME_KEY=secret-key --rm --interactive --tty --volume $PWD/code.tar.gz:/tmp/code.tar.gz:ro openruntimes/dart:v2-2.17 sh /usr/local/src/start.sh | ||
``` | ||
|
||
Your function is now listening on port `3000`, and you can execute it by sending `POST` request with appropriate | ||
authorization headers. To learn more about runtime, you can visit Dart | ||
runtime [README](https://github.com/open-runtimes/open-runtimes/tree/main/runtimes/dart-2.17). | ||
|
||
4. Execute function: | ||
|
||
```shell | ||
curl http:https://localhost:3000/ -d '{"variables":{"FIREBASE_AUTH_KEY":"YOUR_FIREBASE_AUTH_KEY"},"payload":"{\"user_token\":\"USER_FCM_TOKEN\"}"}' -H "X-Internal-Challenge: secret-key" -H "Content-Type: application/json" | ||
``` | ||
|
||
## 📝 Notes | ||
|
||
* This function is designed for use with Appwrite Cloud Functions. You can learn more about it | ||
in [Appwrite docs](https://appwrite.io/docs/functions). | ||
* This example is compatible with Dart 2.17. Other versions may work but are not guaranteed to work as they haven't been | ||
tested. Versions below Dart 2.14 will not work, because Apwrite SDK requires Dart 2.14, |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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,28 @@ | ||
import 'dart:convert'; | ||
|
||
import 'package:http/http.dart' as http; | ||
|
||
class FCMService { | ||
Future<bool> sendFCMToUser( | ||
{required String firebaseAuthKey, | ||
required String userFCMToken, | ||
required Map<String, dynamic> notificationData}) async { | ||
try { | ||
await http.post( | ||
Uri.parse('https://fcm.googleapis.com/fcm/send'), | ||
headers: <String, String>{ | ||
'Content-Type': 'application/json; charset=UTF-8', | ||
'Authorization': 'key=$firebaseAuthKey' | ||
}, | ||
body: jsonEncode(<String, dynamic>{ | ||
'to': userFCMToken, | ||
'notification': notificationData | ||
}), | ||
); | ||
} catch (e) { | ||
print(e); | ||
return false; | ||
} | ||
return true; | ||
} | ||
} |
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,71 @@ | ||
import 'dart:convert'; | ||
|
||
import 'fcm_service.dart'; | ||
|
||
void returnSuccess(final res, final message) { | ||
res.json({ | ||
'success': true, | ||
'message': message, | ||
}, status: 200); | ||
} | ||
|
||
void returnFailure(final res, final String message) { | ||
res.json({ | ||
'success': false, | ||
'message': message, | ||
}, status: 500); | ||
} | ||
|
||
bool checkEnvVariables(final req, final res) { | ||
if (req.variables['APPWRITE_FUNCTION_ENDPOINT'] == null || | ||
req.variables['APPWRITE_FUNCTION_API_KEY'] == null || | ||
req.variables['APPWRITE_FUNCTION_PROJECT_ID'] == null || | ||
req.variables['APPWRITE_FUNCTION_USER_ID'] == null || | ||
req.variables['FIREBASE_AUTH_KEY'] == null) { | ||
returnFailure(res, "Some Environment variables are not set"); | ||
return false; | ||
} | ||
return true; | ||
} | ||
|
||
bool checkPayload(Map<String, dynamic> payload, final res) { | ||
if (payload['user_token'] == null) { | ||
returnFailure(res, "Payload has incorrect data, user_token is empty"); | ||
return false; | ||
} | ||
return true; | ||
} | ||
|
||
FCMService? mockFCMService; | ||
|
||
Future<void> start(final req, final res) async { | ||
if (!checkEnvVariables(req, res)) { | ||
return; | ||
} | ||
final String firebaseAuthKey = req.variables['FIREBASE_AUTH_KEY']; | ||
|
||
final payload = jsonDecode(req.payload == '' ? '{}' : req.payload); | ||
if (!checkPayload(payload, res)) { | ||
return; | ||
} | ||
|
||
final String userToken = payload['user_token']; | ||
|
||
final fcmService = mockFCMService ?? FCMService(); | ||
|
||
final Map<String, dynamic> notificationData = { | ||
'Title': "Awesome title!", | ||
'Body': "Awesome body", | ||
}; | ||
|
||
final isPushSent = await fcmService.sendFCMToUser( | ||
firebaseAuthKey: firebaseAuthKey, | ||
userFCMToken: userToken, | ||
notificationData: notificationData); | ||
|
||
if (isPushSent) { | ||
returnSuccess(res, "Push notification successfully sent"); | ||
} else { | ||
returnFailure(res, "Error while sending push notification"); | ||
} | ||
} |
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,13 @@ | ||
name: send_push_notification | ||
description: "" | ||
version: 1.0.0 | ||
|
||
environment: | ||
sdk: '>=2.17.0 <3.0.0' | ||
|
||
dependencies: | ||
http: ^0.13.5 | ||
|
||
dev_dependencies: | ||
test: ^1.21.4 | ||
mocktail: ^0.3.0 |
96 changes: 96 additions & 0 deletions
96
dart/send_push_notification/test/send_push_notification_test.dart
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,96 @@ | ||
import 'dart:convert' as convert; | ||
|
||
import 'package:mocktail/mocktail.dart'; | ||
import 'package:send_push_notification/fcm_service.dart'; | ||
import 'package:send_push_notification/main.dart'; | ||
import 'package:test/test.dart'; | ||
|
||
|
||
const fakeFriendDeviceToken = 'fake_friend_device_token'; | ||
const fakeFirebaseAuthKey = 'fake_firebase_auth_key'; | ||
|
||
class Request { | ||
Map<String, dynamic> headers = {}; | ||
String payload = ''; | ||
Map<String, dynamic> variables = { | ||
'APPWRITE_FUNCTION_ENDPOINT': 'fake_url', | ||
'APPWRITE_FUNCTION_PROJECT_ID': 'fake_project_id', | ||
'APPWRITE_FUNCTION_API_KEY': 'fake_api_key', | ||
'APPWRITE_FUNCTION_USER_ID': 'fake_user_id', | ||
'FIREBASE_AUTH_KEY': fakeFirebaseAuthKey | ||
}; | ||
} | ||
|
||
class Response { | ||
String responseAsString = ''; | ||
int statusCode = 0; | ||
|
||
void send(text, {int status = 200}) { | ||
responseAsString = text; | ||
statusCode = status; | ||
} | ||
|
||
void json(obj, {int status = 200}) { | ||
responseAsString = convert.json.encode(obj); | ||
statusCode = status; | ||
} | ||
} | ||
|
||
class MockFCMService extends Mock implements FCMService {} | ||
|
||
void main() { | ||
test('call remote function with incorrect ENV variables', () async { | ||
final req = Request(); | ||
req.variables['FIREBASE_AUTH_KEY'] = null; | ||
|
||
final res = Response(); | ||
await start(req, res); | ||
expect(res.statusCode, 500); | ||
expect(res.responseAsString, | ||
'{"success":false,"message":"Some Environment variables are not set"}'); | ||
}); | ||
|
||
test('call remote function with incorrect payload', () async { | ||
final req = Request(); | ||
req.payload = ''; | ||
|
||
final res = Response(); | ||
await start(req, res); | ||
expect(res.statusCode, 500); | ||
expect(res.responseAsString, | ||
'{"success":false,"message":"Payload has incorrect data, user_token is empty"}'); | ||
}); | ||
|
||
test('mocked services: call remote function with users.create event', | ||
() async { | ||
final req = Request(); | ||
req.payload = '{"user_token": "$fakeFriendDeviceToken"}'; | ||
|
||
final res = Response(); | ||
mockFCMService = MockFCMService(); | ||
|
||
when(() => mockFCMService!.sendFCMToUser( | ||
firebaseAuthKey: fakeFirebaseAuthKey, | ||
userFCMToken: fakeFriendDeviceToken, | ||
notificationData: any(named: 'notificationData'), | ||
)).thenAnswer((invocation) async => true); | ||
|
||
await start(req, res); | ||
|
||
final capturedFCMDataArgs = verify(() => mockFCMService!.sendFCMToUser( | ||
firebaseAuthKey: fakeFirebaseAuthKey, | ||
userFCMToken: captureAny(named: 'userFCMToken'), | ||
notificationData: captureAny(named: 'notificationData'), | ||
)).captured; | ||
|
||
expect(capturedFCMDataArgs[0], fakeFriendDeviceToken); | ||
|
||
final pushData = capturedFCMDataArgs[1]; | ||
expect(pushData['Title'], 'Awesome title!'); | ||
expect(pushData['Body'], 'Awesome body'); | ||
|
||
expect(res.statusCode, 200); | ||
expect(res.responseAsString, | ||
'{"success":true,"message":"Push notification successfully sent"}'); | ||
}); | ||
} |