Sử dụng Firebase Realtime Database để gửi thông báo cho người dùng trong web app

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

image

Step 2

image

Step 3

image

Add new firebase realtime database

image

image

Select your database region

image

Test Mode Enable

image

Finally, you will have this new database with the generated URI

image

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

image

After imported

image

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

  1. Security rules
"notifications": {
  "$userId": {
        "notificationId": 1,
        "content": "hello",
        "notificationType": 1,
        "isRead": false,
        "isSent": false
   },
}

image

  1. 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

  1. Enable Authentication on Firebase

Access your firebase console and enable authentication product

image

  1. Create custom Auth

image

  1. Get configuration

5.1. Generate admin service account to use in your API Server

image

5.2. Generate app configuration

Add app -> select web app

image

image

// 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,
  }
);
  1. 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>
    </>
  );
}