Server
Cài đặt expressjs + ejs bằng dòng lệnh
express -e
Cài đặt thêm module body-parser, webpush, dotenv
yarn add dotenv, body-parser, web-push
Ở file app.js sẽ được
require('dotenv').config({ path: `.env` }); var createError = require('http-errors'); var path = require('path'); var cookieParser = require('cookie-parser'); var logger = require('morgan'); var debug = require('debug')('vision-notification:server'); var http = require('http'); const bodyParser = require('body-parser'); var express = require('express'); var app = express(); var server = http.createServer(app); var indexRouter = require('./routes/index'); var usersRouter = require('./routes/users'); // view engine setup app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'ejs'); app.use(bodyParser.json()); app.use(logger('dev')); app.use(express.json()); app.use(express.urlencoded({ extended: false })); app.use(cookieParser()); app.use(express.static(path.join(__dirname, 'public'))); app.use('/', indexRouter); app.use('/users', usersRouter); // catch 404 and forward to error handler app.use(function(req, res, next) { next(createError(404)); }); // error handler app.use(function(err, req, res, next) { // set locals, only providing error in development res.locals.message = err.message; res.locals.error = req.app.get('env') === 'development' ? err : {}; // render the error page res.status(err.status || 500); res.render('error'); }); server.listen(process.env.PORT || 3000);
Tạo file .env với các biến môi trường
PORT = 5005 PUBLIC_VAPID_KEY="BJ8MNpjj2_cNG02uDP3WcKEaI1dR4hUIERoEqI9RjLGeRIV8Ziy2TSmUuv9T2YrQKLgnWajN8ACoDaBMrFsQVRg" PRIVATE_VAPID_KEY="ISRHeHIdMvK83dqN_srrqSpjrv1G3u80lXu08TrNgd4"
Lưu ý publickey và private key có thể tạo tại trang: https://vapidkeys.com/
Tại file index.js ở thư mục routes chúng ta sẽ thêm api để đăng ký nhận thông báo và gửi thông báo
var express = require('express'); var router = express.Router(); const webPush = require('web-push'); const publicVapidKey = process.env.PUBLIC_VAPID_KEY; const privateVapidKey = process.env.PRIVATE_VAPID_KEY; webPush.setVapidDetails('mailto:[email protected]', publicVapidKey, privateVapidKey); let list_sub = []; //lưu lại danh sách các thiết bị nhận thông báo /* GET home page. */ router.get('/', function(req, res, next) { res.render('index'); }); router.post('send-notification', async function(req, res, next) { //send to notification const payload = JSON.stringify({ title: `Tiêu đề thông báo`, body: 'Nội dung thông báo' }); //lặp các phần tử của danh sách thông báo đã lưu để gửi thông báo for(let i = 0; i <list_sub.length; i++) { webPush.sendNotification(list_sub[i], payload) .catch(error => console.error(error)); } } return res.send({status: true}); }); //đăng ký nhận thông báo router.post('/subscribe', (req, res) => { const subscription = req.body; //lưu người đăng ký nhận thông báo vào danh sách list_sub.push(subscription); res.status(201).json({}); const payload = JSON.stringify({ title: 'Đăng ký nhận thông tin trạng thái hệ thống thành công!', body: 'Bật Chrome để nhận được thông báo' }); webPush.sendNotification(subscription, payload) .catch(error => console.error(error)); }); module.exports = router;
Client
Mở thư mục public, tạo thư mục javascripts và 2 file main.js và sw.js nằm bên trong nó
File main.js sẽ chứa nội dung
const publicVapidKey = "BJ8MNpjj2_cNG02uDP3WcKEaI1dR4hUIERoEqI9RjLGeRIV8Ziy2TSmUuv9T2YrQKLgnWajN8ACoDaBMrFsQVRg"; async function triggerPushNotification() { if ('serviceWorker' in navigator) { await navigator.serviceWorker.register('/javascripts/sw.js', { scope: '/javascripts/' }).then(reg => { reg.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: urlBase64ToUint8Array(publicVapidKey), }).then( // (B3-1) OK - TEST PUSH NOTIFICATION sub => { fetch("/subscribe", { method: "POST", body: JSON.stringify(sub), headers: { "content-type": "application/json" } }) .then(res => res.text()) .then(txt => console.log(txt)) .catch(err => console.error(err)); }, // (B3-2) ERROR! err => console.error(err) ); }); } else { console.error('Service workers are not supported in this browser'); } } triggerPushNotification(); function urlBase64ToUint8Array(base64String) { const padding = '='.repeat((4 - base64String.length % 4) % 4); const base64 = (base64String + padding) .replace(/-/g, '+') .replace(/_/g, '/'); const rawData = window.atob(base64); const outputArray = new Uint8Array(rawData.length); for (let i = 0; i < rawData.length; ++i) { outputArray[i] = rawData.charCodeAt(i); } return outputArray; }
Lưu ý: publickey phải giống với publickey lưu trong file .env
Nội dung của file sw.js
self.addEventListener('push', event => { const data = event.data.json(); self.registration.showNotification(data.title, { body: data.body, }); });
Cuối cùng mở thư mục views và sửa file index.ejs như sau
<!DOCTYPE html> <html> <head> <title>Notifications</title> <script src="/javascripts/main.js"></script> </head> <body> <h1>Notifications</h1> </body> </html>
Chạy server và kiểm tra kết quả