Sử dụng Firebase Realtime Database để gửi thông báo cho người dùng trong web app
Post Date : 2022-07-12T17:09:16+07:00
Modified Date : 2022-07-12T17:09:16+07:00
Category: cheatsheet
Tags:
Firebase Realtime Database
Create new project
Step 1
Step 2
Step 3
Add new firebase realtime database
Select your database region
Test Mode Enable
Finally, you will have this new database with the generated URI
Let’s design a simple notification database
"notifications": {
"$userId": {
"notificationId": 1,
"content": "hello",
"notificationType": 1,
"isRead": false,
"isSent": false
},
}
{
"notifications": {
"1": [
{
"userId": 1,
"notificationId": 1,
"content": "hello",
"notificationType": 1,
"isRead": false,
"isSent": false
},
{
"userId": 1,
"notificationId": 2,
"content": "hey",
"notificationType": 2,
"isRead": false,
"isSent": false
}
],
"2": [
{
"userId": 2,
"notificationId": 3,
"content": "heyo",
"notificationType": 1,
"isRead": false,
"isSent": false
}
]
}
}
Import
After imported
Connect and get DB instance
/* eslint-disable import/no-anonymous-default-export */
import { initializeApp } from "firebase/app";
import { getDatabase } from "firebase/database";
export default {
getInstance: (databaseURL) => {
const firebaseConfig = {
// ...
// The value of `databaseURL` depends on the location of the database
databaseURL,
};
// Initialize Firebase
const app = initializeApp(firebaseConfig);
// Initialize Realtime Database and get a reference to the service
return getDatabase(app);
},
};
Subscribe for changes
function App() {
const [notifications, setNotifications] = useState([]);
const subcribeOnNotification = () => {
const db = firebaseService.getInstance(FIREBASE_DATABASE_URL);
const userId = 1;
const dbRef = ref(db, "/notifications/" + userId);
onValue(
dbRef,
(snapshot) => {
if (snapshot.val()) {
setNotifications([...notifications, ...snapshot.val()]);
}
},
{
onlyOnce: false,
}
);
};
// init
useEffect(() => {
subcribeOnNotification();
}, []);
return (
<>
<h1>Firebase Realtime Database Notififcation Sample</h1>
<ul>
{notifications.map((n) => (
<li key={n.notificationId}>{JSON.stringify(n)}</li>
))}
</ul>
</>
);
}
export default App;
Secure your firebase database
- Security rules
- Ref : https://firebase.google.com/docs/database/security/rules-conditions
Sample: User can only read his records
"notifications": {
"$userId": {
"notificationId": 1,
"content": "hello",
"notificationType": 1,
"isRead": false,
"isSent": false
},
}
- In this case user must login with firebase to allow they can access their record
There are several ways to authenticate a user with firebase. In this case we will use custom token through our API server to shared the same user id of our system with firebase
- Enable Authentication on Firebase
Access your firebase console and enable authentication product
- Create custom Auth
- Get configuration
5.1. Generate admin service account to use in your API Server
5.2. Generate app configuration
Add app -> select web app
// Initialize Firebase connection
import { onValue, ref } from "firebase/database";
import {
getAuth,
onAuthStateChanged,
signInWithCustomToken,
} from "firebase/auth";
import { getDatabase } from "firebase/database";
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
const db = getDatabase(app);
// login
signInWithCustomToken(auth, customToken)
.then((userCredential) => {
console.log(userCredential);
})
.catch((error) => {
console.error(error);
});
// listen on login state
onAuthStateChanged(auth, (user) => {
// user = null | Object
if (user) {
// do anything you want
}
});
// db manipulation : listen on change
const userId = 1;
const dbRef = ref(db, "/notifications/" + userId);
onValue(
dbRef,
(snapshot) => {
if (snapshot.val()) {
setNotifications([...notifications, ...snapshot.val()]);
}
},
{
onlyOnce: false,
}
);
- Samples
Backend API
const admin = require("firebase-admin");
const serviceAccount = require("./jsguru-firebase-admin-sdk.json");
var auth = null;
// connect Firebase
const app = admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
});
auth = admin.auth(app);
const getAuth = () => {
return auth;
};
app.post("/generate-firebase-token", async (req, res) => {
const userId = "1";
const additionalClaims = {
premiumAccount: false,
};
// Custom auth without generate your custom JWT
await new Promise((resolve) => {
getAuth()
.createCustomToken(userId, additionalClaims)
.then((customToken) => {
// Send token back to client
resolve(customToken);
res.send({
customToken,
});
})
.catch((error) => {
res.send({
customToken: "",
});
console.log("Error creating custom token:", error);
});
});
});
Application
// firebase.service.js
/* eslint-disable import/no-anonymous-default-export */
import { initializeApp } from "firebase/app";
import { getDatabase } from "firebase/database";
export default {
getInstance: () => {
const firebaseConfig = {
// ...
// The value of `databaseURL` depends on the location of the database
apiKey: `${process.env.REACT_APP_apiKey}`,
authDomain: `${process.env.REACT_APP_authDomain}`,
databaseURL: `${process.env.REACT_APP_databaseURL}`,
projectId: `${process.env.REACT_APP_projectId}`,
messagingSenderId: `${process.env.REACT_APP_messagingSenderId}`,
appId: `${process.env.REACT_APP_appId}`,
};
// Initialize Firebase
const app = initializeApp(firebaseConfig);
// Initialize Realtime Database and get a reference to the service
return {
app,
db: getDatabase(app),
};
},
};
// App.jsx
async function postData(url = "", data = {}) {
// Default options are marked with *
const response = await fetch(url, {
method: "POST", // *GET, POST, PUT, DELETE, etc.
mode: "cors", // no-cors, *cors, same-origin
cache: "no-cache", // *default, no-cache, reload, force-cache, only-if-cached
credentials: "same-origin", // include, *same-origin, omit
headers: {
"Content-Type": "application/json",
// 'Content-Type': 'application/x-www-form-urlencoded',
},
redirect: "follow", // manual, *follow, error
referrerPolicy: "no-referrer", // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
body: JSON.stringify(data), // body data type must match "Content-Type" header
});
return response.json(); // parses JSON response into native JavaScript objects
}
const getFirebaseToken = () => {
return postData("http://localhost:1337/login");
};
const { app, db } = firebaseService.getInstance();
const auth = getAuth(app);
function App() {
const [notifications, setNotifications] = useState([]);
const doLogin = async () => {
const res = await getFirebaseToken();
console.log(auth);
console.log(app);
signInWithCustomToken(auth, res.customToken)
.then((userCredential) => {
console.log(userCredential);
})
.catch((error) => {
console.error(error);
});
};
const subcribeOnNotification = () => {
const userId = 1;
const dbRef = ref(db, "/notifications/" + userId);
onValue(
dbRef,
(snapshot) => {
if (snapshot.val()) {
setNotifications([...notifications, ...snapshot.val()]);
}
},
{
onlyOnce: false,
}
);
};
// init
useEffect(() => {
onAuthStateChanged(auth, (user) => {
console.log("user", user);
if (user) {
// do something with realtime database
subcribeOnNotification();
}
});
}, []);
return (
<>
<h1>Firebase Realtime Database Notififcation Sample</h1>
<ul>
{notifications.map((n) => (
<li key={n.notificationId}>{JSON.stringify(n)}</li>
))}
</ul>
<button onClick={() => doLogin()}>Login</button>
</>
);
}