Skip to content

Commit

Permalink
Added dart example of push notification function
Browse files Browse the repository at this point in the history
  • Loading branch information
dtikhonov-gd committed Mar 1, 2023
1 parent 988909f commit 2ace6b7
Show file tree
Hide file tree
Showing 7 changed files with 303 additions and 0 deletions.
95 changes: 95 additions & 0 deletions dart/send_push_notification/README.md
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.
28 changes: 28 additions & 0 deletions dart/send_push_notification/lib/fcm_service.dart
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;
}
}
71 changes: 71 additions & 0 deletions dart/send_push_notification/lib/main.dart
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");
}
}
13 changes: 13 additions & 0 deletions dart/send_push_notification/pubspec.yaml
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 dart/send_push_notification/test/send_push_notification_test.dart
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"}');
});
}

0 comments on commit 2ace6b7

Please sign in to comment.