Skip to content

Commit da5f60c

Browse files
Python version of FCM sample (#1108)
* Python version of FCM sample * Format with black * none more black
1 parent 3313875 commit da5f60c

File tree

12 files changed

+652
-0
lines changed

12 files changed

+652
-0
lines changed

Python/fcm-notifications/.gitignore

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
yarn-debug.log*
6+
yarn-error.log*
7+
firebase-debug.log*
8+
firebase-debug.*.log*
9+
10+
# Firebase cache
11+
.firebase/
12+
13+
# Firebase config
14+
15+
# Uncomment this if you'd like others to create their own Firebase project.
16+
# For a team working on the same Firebase project(s), it is recommended to leave
17+
# it commented so all members can deploy to the same project(s) in .firebaserc.
18+
# .firebaserc
19+
20+
# Runtime data
21+
pids
22+
*.pid
23+
*.seed
24+
*.pid.lock
25+
26+
# Directory for instrumented libs generated by jscoverage/JSCover
27+
lib-cov
28+
29+
# Coverage directory used by tools like istanbul
30+
coverage
31+
32+
# nyc test coverage
33+
.nyc_output
34+
35+
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
36+
.grunt
37+
38+
# Bower dependency directory (https://bower.io/)
39+
bower_components
40+
41+
# node-waf configuration
42+
.lock-wscript
43+
44+
# Compiled binary addons (http://nodejs.org/api/addons.html)
45+
build/Release
46+
47+
# Dependency directories
48+
node_modules/
49+
50+
# Optional npm cache directory
51+
.npm
52+
53+
# Optional eslint cache
54+
.eslintcache
55+
56+
# Optional REPL history
57+
.node_repl_history
58+
59+
# Output of 'npm pack'
60+
*.tgz
61+
62+
# Yarn Integrity file
63+
.yarn-integrity
64+
65+
# dotenv environment variables file
66+
.env

Python/fcm-notifications/README.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Send Firebase Cloud Messaging notifications for new followers.
2+
3+
This sample demonstrates how to send a Firebase Cloud Messaging (FCM) notification from a Realtime Database triggered Function. The sample also features a Web UI to experience the FCM notification.
4+
5+
6+
## Functions Code
7+
8+
See file [functions/main.py](functions/main.py) for the code.
9+
10+
Sending the notification is done using the [Firebase Admin SDK](https://firebase.google.com/docs/admin/setup). The Web client writes the individual device tokens to the realtime database which the Function uses to send the notification.
11+
12+
The dependencies are listed in [functions/requirements.txt](functions/requirements.txt).
13+
14+
15+
## Sample Database Structure
16+
17+
Users sign into the app and are requested to enable notifications on their browsers. If they successfully enable notifications the device token is saved into the datastore under `/users/$uid/notificationTokens`.:
18+
19+
```
20+
/functions-project-12345
21+
/users
22+
/Uid-12345
23+
displayName: "Bob Dole"
24+
/notificationTokens
25+
1234567890: true
26+
photoURL: "https://lh3.googleusercontent.com/..."
27+
28+
```
29+
30+
If a user starts following another user we'll write to `/followers/$followedUid/$followerUid`:
31+
32+
```
33+
/functions-project-12345
34+
/followers
35+
/followedUid-12345
36+
followerUid-67890: true
37+
/users
38+
/Uid-12345
39+
displayName: "Bob Dole"
40+
/notificationTokens
41+
1234567890: true
42+
photoURL: "https://lh3.googleusercontent.com/..."
43+
44+
```
45+
46+
47+
## Trigger rules
48+
49+
The function triggers every time the value of a follow flag changes at `/followers/$followedUid/$followerUid`.
50+
51+
52+
## Deploy and test
53+
54+
This sample comes with a web-based UI for testing the function. To test it out:
55+
56+
1. Set up your Firebase project:
57+
1. [Create a Firebase project](https://firebase.google.com/docs/web/setup/#create-firebase-project)
58+
1. [Register your web app with Firebase](https://firebase.google.com/docs/web/setup/#register-app)
59+
1. Enable **Google Provider** in the [Auth section](https://console.firebase.google.com/project/_/authentication/providers)
60+
1. Clone or download this repo and open the `fcm-notification` directory.
61+
1. You must have the Firebase CLI installed. If you don't have it install it with `npm install -g firebase-tools` and then configure it with `firebase login`.
62+
1. Configure the CLI locally by using `firebase use --add` and select your project in the list.
63+
1. Install dependencies locally by running: `./functions/venv/bin/pip install -r functions/requirements.txt`
64+
1. Deploy your project using `firebase deploy`
65+
1. Open the app using `firebase open hosting:site`, this will open a browser.
66+
1. Start following a user, this will send a notification to them.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"rules": {
3+
"users": {
4+
".read": true,
5+
"$uid": {
6+
".write": "auth.uid === $uid"
7+
}
8+
},
9+
"followers": {
10+
"$followedUid": {
11+
"$followerUid": {
12+
".read": "auth.uid === $followerUid",
13+
".write": "auth.uid === $followerUid"
14+
}
15+
}
16+
}
17+
}
18+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"database": {
3+
"rules": "database.rules.json"
4+
},
5+
"hosting": {
6+
"public": "public"
7+
},
8+
"functions": [
9+
{
10+
"source": "functions",
11+
"codebase": "fcm-notifications",
12+
"ignore": [
13+
"venv",
14+
".git",
15+
"firebase-debug.log",
16+
"firebase-debug.*.log"
17+
]
18+
}
19+
]
20+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
__pycache__
2+
venv
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import firebase_admin
2+
from firebase_admin import auth, db, messaging
3+
from firebase_functions import db_fn
4+
5+
firebase_admin.initialize_app()
6+
7+
8+
@db_fn.on_value_written(reference=r"followers/{followedUid}/{followerUid}")
9+
def send_follower_notification(event: db_fn.Event[db_fn.Change]) -> None:
10+
"""Triggers when a user gets a new follower and sends a notification.
11+
12+
Followers add a flag to `/followers/{followedUid}/{followerUid}`.
13+
Users save their device notification tokens to
14+
`/users/{followedUid}/notificationTokens/{notificationToken}`.
15+
"""
16+
follower_uid = event.params["followerUid"]
17+
followed_uid = event.params["followedUid"]
18+
19+
# If un-follow we exit the function.
20+
change = event.data
21+
if not change.after:
22+
print(f"User {follower_uid} unfollowed user {followed_uid} :(")
23+
return
24+
25+
print(f"User {follower_uid} is now following user {followed_uid}")
26+
tokens_ref = db.reference(f"users/{followed_uid}/notificationTokens")
27+
notification_tokens = tokens_ref.get()
28+
if (
29+
not isinstance(notification_tokens, dict)
30+
or len(notification_tokens) < 1
31+
):
32+
print("There are no tokens to send notifications to.")
33+
return
34+
print(
35+
f"There are {len(notification_tokens)} tokens to send notifications to."
36+
)
37+
38+
follower: auth.UserRecord = auth.get_user(follower_uid)
39+
notification = messaging.Notification(
40+
title="You have a new follower!",
41+
body=f"{follower.display_name} is now following you.",
42+
image=follower.photo_url if follower.photo_url else "",
43+
)
44+
45+
# Send notifications to all tokens.
46+
msgs = [
47+
messaging.Message(token=token, notification=notification)
48+
for token in notification_tokens
49+
]
50+
batch_response: messaging.BatchResponse = messaging.send_each(msgs)
51+
if batch_response.failure_count < 1:
52+
# Messages sent sucessfully. We're done!
53+
return
54+
# Clean up the tokens that are not registered any more.
55+
for response in batch_response.responses:
56+
if response.exception.code in (
57+
"messaging/invalid-registration-token",
58+
"messaging/registration-token-not-registered",
59+
):
60+
tokens_ref.child(response.message_id).delete()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
firebase_functions~=0.1.0
3.46 KB
Loading
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/**
2+
* Copyright 2023 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
// Import and configure the Firebase SDK
18+
// These scripts are made available when the app is served or deployed on Firebase Hosting
19+
// If you do not serve/host your project using Firebase Hosting see https://firebase.google.com/docs/web/setup
20+
importScripts('/__/firebase/10.0.0/firebase-app-compat.js');
21+
importScripts('/__/firebase/10.0.0/firebase-messaging-compat.js');
22+
importScripts('/__/firebase/init.js');
23+
24+
firebase.messaging();
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
<!doctype html>
2+
<!--
3+
Copyright 2016 Google Inc. All rights reserved.
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
https://www.apache.org/licenses/LICENSE-2.0
8+
Unless required by applicable law or agreed to in writing, software
9+
distributed under the License is distributed on an "AS IS" BASIS,
10+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
See the License for the specific language governing permissions and
12+
limitations under the License
13+
-->
14+
<html lang="en">
15+
<head>
16+
<meta charset="utf-8">
17+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
18+
<meta name="description" content="Demonstrates of to authorize Firebase with Instagram Auth using Firebase Functions">
19+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
20+
<title>Firebase Functions demo send FCM notifications</title>
21+
22+
<!-- Material Design Lite -->
23+
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
24+
<link rel="stylesheet" href="https://code.getmdl.io/1.1.3/material.blue_grey-orange.min.css">
25+
<script defer src="https://code.getmdl.io/1.1.3/material.min.js"></script>
26+
27+
<link rel="stylesheet" href="main.css">
28+
</head>
29+
<body>
30+
<div class="demo-layout mdl-layout mdl-js-layout mdl-layout--fixed-header">
31+
32+
<!-- Header section containing title -->
33+
<header class="mdl-layout__header mdl-color-text--white mdl-color--light-blue-700">
34+
<div class="mdl-cell mdl-cell--12-col mdl-cell--12-col-tablet mdl-grid">
35+
<div class="mdl-layout__header-row mdl-cell mdl-cell--12-col mdl-cell--12-col-tablet mdl-cell--8-col-desktop">
36+
<h3>Send FCM notifications demo</h3>
37+
</div>
38+
</div>
39+
</header>
40+
<main class="mdl-layout__content mdl-color--grey-100">
41+
<div class="mdl-cell--12-col mdl-cell--12-col-tablet mdl-grid">
42+
43+
<!-- Card containing the sign-in UI -->
44+
<div id="demo-signed-out-card" class="mdl-card mdl-shadow--2dp mdl-cell">
45+
<div class="mdl-card__supporting-text mdl-color-text--grey-600">
46+
<p>
47+
This web application demonstrates how you can send FCM notifications using Functions and a web client.
48+
<strong>Now sign in and activate notifications for your user!</strong>
49+
</p>
50+
<button id="demo-sign-in-button" class="mdl-color-text--grey-700 mdl-button--raised mdl-button mdl-js-button mdl-js-ripple-effect"><i class="material-icons">account_circle</i> Sign in with Google</button>
51+
</div>
52+
</div>
53+
54+
<!-- Card containing the signed-in UI -->
55+
<div id="demo-signed-in-card" class="mdl-card mdl-shadow--2dp mdl-cell">
56+
<div class="mdl-card__supporting-text mdl-color-text--grey-600">
57+
<p>
58+
Welcome <span id="demo-name-container"></span>
59+
</p>
60+
<div id="demo-fcm-error-container"></div>
61+
<button id="demo-sign-out-button" class="mdl-color-text--grey-700 mdl-button--raised mdl-button mdl-js-button mdl-js-ripple-effect">Sign out</button>
62+
<button id="demo-delete-button" class="mdl-color-text--grey-700 mdl-button--raised mdl-button mdl-js-button mdl-js-ripple-effect">Delete account</button>
63+
</div>
64+
</div>
65+
66+
<!-- Card containing the users to follow -->
67+
<div id="demo-all-users-card" class="mdl-card mdl-shadow--2dp mdl-cell mdl-cell--9-col">
68+
<div class="mdl-card__supporting-text mdl-color-text--grey-600">
69+
<p>
70+
Here are all the users. If they have enabled notifications they will get a notification when you start following them.
71+
</p>
72+
</div>
73+
<div id="demo-all-users-list"></div>
74+
</div>
75+
</div>
76+
77+
<!-- Snackbar -->
78+
<div id="demo-snackbar" aria-live="assertive" aria-atomic="true" aria-relevant="text" class="mdl-snackbar mdl-js-snackbar">
79+
<div class="mdl-snackbar__text"></div>
80+
<button type="button" class="mdl-snackbar__action"></button>
81+
</div>
82+
</main>
83+
</div>
84+
85+
86+
<!-- Import and configure the Firebase SDK -->
87+
<!-- These scripts are made available when the app is served or deployed on Firebase Hosting -->
88+
<!-- If you do not serve/host your project using Firebase Hosting see https://firebase.google.com/docs/web/setup -->
89+
<script src="/__/firebase/10.0.0/firebase-app-compat.js"></script>
90+
<script src="/__/firebase/10.0.0/firebase-auth-compat.js"></script>
91+
<script src="/__/firebase/10.0.0/firebase-messaging-compat.js"></script>
92+
<script src="/__/firebase/10.0.0/firebase-database-compat.js"></script>
93+
<script src="/__/firebase/init.js"></script>
94+
95+
<script src="main.js"></script>
96+
</body>
97+
</html>

0 commit comments

Comments
 (0)