Im implementing a notification system for my app and its working well but it is not real time, How can I implement SSE using my setup below
Client Side
const [fetchnotification, setfetchnotification] = useState('');
const getadminnotif = async()=>{
const api = axios.create({
baseURL: http://localhost:3000/books,
withCredentials: true,
headers: { 'Access-Control-Allow-Origin': '*', 'Content-Type': 'application/json' }
});
const query = await api.get(`/adminNotifications?page=${notifpage}`)
setfetchnotification(query.data.findnotifs)
}
useEffect(async()=>{
getadminnotif()
})
return(
// here i mapped the fetchnotification array to get the notifications
)
Server Side
here im using express router to get the api calls from client
const {getadminNotifs} = require('../controllers/bookControllers');
router.get('/adminNotifications', getadminNotifs )
getadminNotifs
const getadminNotifs = asyncHandler(async (req, res) => {
const page = parseInt(req.query.page) || 1;
const pageSize = 4;
const skip = (page - 1) * pageSize;
const findallnotifs = await AdminNotif.find().sort({'dateAdded':-1}).exec()
const findnotifs = await AdminNotif.find().sort({'dateAdded':-1}).skip(skip).limit(pageSize).exec()
const notiflength = findallnotifs.length
var unreadcounts = findallnotifs.filter(({ status }) => status === 'unread').length
const pages = Math.ceil(notiflength / pageSize)
res.status(200).json({ findnotifs, notiflength, pages, unreadcounts })
})
what ive tried so far is this but its not working
I changed the getadminotif function to call an eventsource and then on the client side,instead of using router, i fetched the api calls in app.js
const getadminnotif = async()=>{
const eventSource = new EventSource(`http://localhost:3000/realtime`,{
headers: {
Accept: "text/event-stream",
},
});
eventSource.onmessage = (e) => setfetchnotification(e.data.findnotifs);
eventSource.onerror = err => {
console.log('EventSource error: ', err);
};
return () => {
eventSource.close();
};
}
app.get("/realtime", function (req, res) {
res.writeHead(200, {
Connection: "keep-alive",
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
});
setInterval(() => {
res.write(
`data: ${getadminNotifs}`
);
res.write("\n\n");
}, 5000);
});
Related
I tried to append push notification to a Nextjs app. Referring to this tutorial but the push notification is not showing.
https://www.youtube.com/watch?v=HlYFW2zaYQM
Of course I turn on authorization of notification in chrome browser.
_app.tsx
import { AppProps } from "next/app"
import { useEffect } from "react"
const publicVapidKey = 'blablabla...'
const send = async () => {
console.log("registering serviceWorker")
const register = await navigator.serviceWorker.register("/sw.js", {
scope: "/",
})
console.log("serviceWorker is registered")
console.log("registering push")
const subscription = await register.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(publicVapidKey),
})
console.log("push is registered")
console.log("sending push..")
await fetch("http://localhost:8000/push", {
method: "POST",
body: JSON.stringify(subscription),
headers: {
"content-type": "application/json",
},
})
console.log("have sent push")
}
// change base64 of VapidKey to Uint8Array
const urlBase64ToUint8Array = (base64String: string) => {
let padding = "=".repeat((4 - (base64String.length % 4)) % 4)
let base64 = (base64String + padding).replace(/\-/g, "+").replace(/_/g, "/")
let rawData = window.atob(base64)
let outputArray = new Uint8Array(rawData.length)
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i)
}
return outputArray
}
function MyApp({ Component, pageProps }: AppProps) {
const { globalIsModalOpen } = useStore()
useEffect(() => {
(async () => {
const result = await Notification.requestPermission()
if (result === "granted") {
console.log("auth ok")
}
})()
if ("serviceWorker" in navigator) {
send().catch((err) => console.error(err))
}
}, [])
return (
<div>
This is example
</div>
)
}
export default MyApp
public/sw.js
self.addEventListener("push", (e) => {
const data = e.data.json();
console.log("have got push notification");
self.registration.showNotification(data.title, {
body: "This is example",
icon:
"https://blablabla...",
});
});
index.js (backend file, separated from Nextjs directory)
const fetch = require('node-fetch')
const express = require('express')
const cors = require('cors')
const webpush = require('web-push')
const app = express()
app.use(cors())
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
const publicVapidKey = 'blablabla...';
const privateVapidKey = 'blablabla...';
webpush.setVapidDetails('mailto:example#example.com', publicVapidKey, privateVapidKey);
app.post('/push', async (req, res) => {
const subscription = req.body;
res.status(201).json({ 'success': true })
const payload = JSON.stringify({ title: 'push notification test' });
webpush.sendNotification(subscription, payload).catch(err => console.error(err))
})
app.listen(8000, () => {
console.log('server is running on 8000')
})
I am trying to use pdf-lib to write to a government PDF form. The form is located on a government website so I have to use a server to server architecture due to CORS. I have set up a lambda function to grab the form and send the PDF Bytes as a response. The problem is when I get it on the front end and make an Object URL it does not appear on the front end as anything I am confused what I am doing wrong.
Here is the lambda code:
const { PDFDocument } = require('pdf-lib');
const axios = require('axios');
/**
* #type {import('#types/aws-lambda').APIGatewayProxyHandler}
*/
exports.handler = async (event) => {
console.log(`EVENT: ${JSON.stringify(event)}`);
async function fillForm() {
const formUrl = 'https://pdf-lib.js.org/assets/dod_character.pdf';
const formPdfBytes = await axios.get(formUrl, {
responseType: 'arraybuffer',
});
const formPdfBytesArrayBuffer = formPdfBytes.data;
const marioUrl = 'https://pdf-lib.js.org/assets/small_mario.png';
const marioImageBytes = await axios.get(marioUrl, {
responseType: 'arraybuffer',
});
const marioImageBytesArrayBuffer = marioImageBytes.data;
const emblemUrl = 'https://pdf-lib.js.org/assets/mario_emblem.png';
const emblemImageBytes = await axios.get(emblemUrl, {
responseType: 'arraybuffer',
});
const emblemImageBytesArrayBuffer = emblemImageBytes.data;
const pdfDoc = await PDFDocument.load(formPdfBytesArrayBuffer);
const marioImage = await pdfDoc.embedPng(marioImageBytesArrayBuffer);
const emblemImage = await pdfDoc.embedPng(emblemImageBytesArrayBuffer);
console.log(marioImage, emblemImage);
const form = pdfDoc.getForm();
console.log(form);
const pdfBytes = await pdfDoc.save();
return pdfBytes;
}
const docURL = await fillForm();
return {
statusCode: 200,
// Uncomment below to enable CORS requests
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': '*',
'Access-Control-Allow-Credentials': true,
'Access-Control-Allow-Methods': 'OPTIONS,POST,GET',
},
body: JSON.stringify({ data: { docURL } }),
};
};
Here is my front end code that is currently not causing anything to display:
const handleButtonClick = async () => {
try {
const pdfBytes = await fetchPDF();
const bytes = new Uint8Array(pdfBytes.docURL);
const blob = new Blob([bytes], { type: 'application/pdf' });
const docUrl = URL.createObjectURL(blob);
setPdf(docUrl);
} catch (err) {
console.log(err);
}
};
No matter what I try, I cannot get my data to print as it comes in. I tried this tutorial:
https://jakearchibald.com/2016/streams-ftw/
This SO post: How to handle streaming data using fetch?
(and a several other SO posts I can't recall)
And, tried reading the docs: https://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Using_readable_streams
test.py
import time
import sys
for i in range(1,10):
print('test', i)
time.sleep(.5)
sys.stdout.flush()
server.js
firmwareRouter.get('/run_test', (req, res) => {
res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-control': 'no-cache' });
const pathToExample = '/server/path/to/test.py';
const { spawn } = require('child_process');
const pythonProcess = spawn('python', [pathToExample]);
pythonProcess.stdout.on('data', (data) => {
// console.log(data.toString());
res.write(data);
});
pythonProcess.on('close', (code) => {
res.end('Process has ended');
});
});
Firmware.js (Version 1)
export default function Firmware(props) {
const [data, setData] = useState('');
async function runTest() {
try {
const url = 'api/firmware/run_test'
const request = {
method: 'GET',
headers: {
'Content-Type': 'text/event-stream'
},
}
const res = await fetch(url, request);
const reader = res.body.getReader();
const chunks = [];
const decoder = new TextDecoder('utf-8');
let done, value;
while (!done) {
({ value, done } = await reader.read());
value = decoder.decode(value);
if (done) {
return chunks;
}
console.log(value);
chunks.push(value);
setData(value);
}
} catch (err) {
console.log('frontend:Firmware', err);
}
}
return (
<Fragment>
{data}
<button onClick={() => runTest()}>Run Test </button>
</Fragment >
)
}
Firmware.js (Version 2)
export default function Firmware(props) {
const [data, setData] = useState('');
async function runTest() {
const url = 'api/firmware/run_test'
const request = {
method: 'GET',
headers: {
'Content-Type': 'text/event-stream'
},
}
fetch(url, request).then((res) => {
let reader = res.body.getReader();
let decoder = new TextDecoder();
reader.read().then(function processResult(result) {
if (result.done) return;
console.log(decoder.decode(result.value, { stream: true }))
return reader.read().then(processResult);
})
})
}
// return ()
No matter what, it only prints to the browser once the process has finished. I need it to print as the python script prints. What am I missing here?
Okay... So, one "solution" is to build it. After I built the app (npm run build), it worked.
Note: I am serving my app from express/node.
It seems to be a webpack or middleware issue between the backend and frontend and using a Proxy. I am going to try to use CORS, turn off React's proxy and go from there.
HHi,
I'm working on a project for an online course and I need to make one change to the project. I don't exactly understand what the code reviewer is saying I'm doing wrong. His comment is:
Here you need to fire a new GET request to fetch the data from server.
The requests GET and POST have a specific purpose.
GET request to fetch data from server/db
POST is used to create new data in server/db
These requests must do the task they are designed for, nothing else.
This is the problem area of my code:
let postData = async(url = '', data = {})=>{
console.log(data);
let temp = data.main.temp;
let zip = document.getElementById('zip').value;
let feelings = document.getElementById('feelings').value;
let date = newDate;
let response = await fetch(url, {
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify( { temp, zip, feelings, date }),
});
try {
const newData = await response.json();
console.log(newData);
document.getElementById("date").innerHTML = newData.date;
document.getElementById("temp").innerHTML = newData.temp;
document.getElementById("content").innerHTML = newData.feelings;
return newData
}catch(error) {
console.log("error", error);
}
}
This is my full code:
app.js:
let apiURL = 'http://api.openweathermap.org/data/2.5/weather?id=524901&APPID=' + apiKey + '&zip=';
const endURL = ',us';
// Create a new date instance dynamically with JS
let d = new Date();
let newDate = d.getMonth()+'.'+ d.getDate()+'.'+ d.getFullYear();
document.getElementById('generate').addEventListener('click', performAction);
let content = document.getElementById('feelings').value;
function performAction(e){
let zip = document.getElementById('zip').value;
let url = apiURL + zip + endURL;
apiCall(url)
.then(async function(data){
console.log(data);
let res = await postData('/', data);
console.log(res);
});
};
const apiCall = async (url) =>{
const res = await fetch(url);
try {
const data = await res.json();
console.log(data)
return data;
} catch(error) {
console.log(error)
}
};
let postData = async(url = '', data = {})=>{
console.log(data);
let temp = data.main.temp;
let zip = document.getElementById('zip').value;
let feelings = document.getElementById('feelings').value;
let date = newDate;
let response = await fetch(url, {
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify( { temp, zip, feelings, date }),
});
try {
const newData = await response.json();
console.log(newData);
document.getElementById("date").innerHTML = newData.date;
document.getElementById("temp").innerHTML = newData.temp;
document.getElementById("content").innerHTML = newData.feelings;
return newData
}catch(error) {
console.log("error", error);
}
}
server.js:
let projectData = {};
// Require Express to run server and routes
const express = require('express');
// Start up an instance of app
const app = express();
/* Middleware*/
//Here we are configuring express to use body-parser as middle-ware.
const bodyParser = require('body-parser');
app.use(express.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
const cors = require('cors');
app.use(cors());
// Cors for cross origin allowance
// Initialize the main project folder
app.use(express.static('website'));
// Setup Server
const port = 8000;
const server = app.listen(port, listening);
function listening(){
console.log(`running on localhost: ${port}`);
};
app.get('/weather', getData);
function getData(req, res){
res.send(projectData)
console.log(projectData)
};
app.route('/')
.get(function (req, res) {
res.sendFile('index.html', {root: 'website'})
})
.post(getWeather);
function getWeather(req, res){
console.log(req.body);
projectData = req.body;
console.log(projectData);
res.status(200).send(projectData);
};
I have no idea what the changes would look like because I wrote this code following the same structure that they taught in the lessons. Any help would be greatly appreciated.
Thanks alot,
Mike
I think you are trying to fetch data from API so he is trying to say that when you are trying to fetch data from api you need GET request.
I figured it out. I needed to write this function:
const updateUI = async () =>{
const res = await fetch('/weather');
try {
const allData = await res.json();
console.log(allData)
document.getElementById("date").innerHTML = allData.date;
document.getElementById("temp").innerHTML = allData.temp;
document.getElementById("content").innerHTML = allData.feelings;
return allData
} catch(error) {
console.log(error)
}
};
and edit my postData function to this:
let postData = async(url = '', data = {})=>{
console.log(data);
let temp = data.main.temp;
let zip = document.getElementById('zip').value;
let feelings = document.getElementById('feelings').value;
let date = newDate;
let response = await fetch(url, {
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify( { temp, zip, feelings, date }),
});
try {
updateUI()
/* const newData = await response.json();
console.log(newData);
document.getElementById("date").innerHTML = newData.date;
document.getElementById("temp").innerHTML = newData.temp;
document.getElementById("content").innerHTML = newData.feelings;
return newData
*/
}catch(error) {
console.log("error", error);
}
}
I am working on solutions using which i can send desktop push notification to subscribed clients.
I have created basic solution in where whenever user click on button i ask user for whether they want to allow notifications for my app or not!
I am getting an error of "Registration failed - permission denied" whenever i click on button for first time.
So that i am not able to get required endpoints to save at backend
Here is my code
index.html
<html>
<head>
<title>PUSH NOT</title>
<script src="index.js"></script>
</head>
<body>
<button onclick="main()">Ask Permission</button>
</body>
</html>
index.js
const check = () => {
if (!("serviceWorker" in navigator)) {
throw new Error("No Service Worker support!");
} else {
console.log("service worker supported")
}
if (!("PushManager" in window)) {
throw new Error("No Push API Support!");
} else {
console.log("PushManager worker supported")
}
};
const registerServiceWorker = async () => {
const swRegistration = await navigator.serviceWorker.register("/service.js?"+Math.random());
return swRegistration;
};
const requestNotificationPermission = async () => {
const permission = await window.Notification.requestPermission();
// value of permission can be 'granted', 'default', 'denied'
// granted: user has accepted the request
// default: user has dismissed the notification permission popup by clicking on x
// denied: user has denied the request.
if (permission !== "granted") {
throw new Error("Permission not granted for Notification");
}
};
const main = async () => {
check();
const swRegistration = await registerServiceWorker();
const permission = await requestNotificationPermission();
};
// main(); we will not call main in the beginning.
service.js
// urlB64ToUint8Array is a magic function that will encode the base64 public key
// to Array buffer which is needed by the subscription option
const urlB64ToUint8Array = base64String => {
const padding = "=".repeat((4 - (base64String.length % 4)) % 4);
const base64 = (base64String + padding)
.replace(/\-/g, "+")
.replace(/_/g, "/");
const rawData = atob(base64);
const outputArray = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
};
const saveSubscription = async subscription => {
console.log("Save Sub")
const SERVER_URL = "http://localhost:4000/save-subscription";
const response = await fetch(SERVER_URL, {
method: "post",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(subscription)
});
return response.json();
};
self.addEventListener("activate", async () => {
try {
const applicationServerKey = urlB64ToUint8Array(
"BFPtpIVOcn2y25il322-bHQIqXXm-OACBtFLdo0EnzGfs-jIGXgAzjY6vNapPb4MM1Z1WuTBUo0wcIpQznLhVGM"
);
const options = { applicationServerKey, userVisibleOnly: true };
const subscription = await self.registration.pushManager.subscribe(options);
console.log(JSON.stringify(subscription))
const response = await saveSubscription(subscription);
} catch (err) {
console.log(err.code)
console.log(err.message)
console.log(err.name)
console.log('Error', err)
}
});
self.addEventListener("push", function(event) {
if (event.data) {
console.log("Push event!! ", event.data.text());
} else {
console.log("Push event but no data");
}
});
Also i have created a bit of backend as well
const express = require("express");
const cors = require("cors");
const bodyParser = require("body-parser");
const webpush = require('web-push')
const app = express();
app.use(cors());
app.use(bodyParser.json());
const port = 4000;
app.get("/", (req, res) => res.send("Hello World!"));
const dummyDb = { subscription: null }; //dummy in memory store
const saveToDatabase = async subscription => {
// Since this is a demo app, I am going to save this in a dummy in memory store. Do not do this in your apps.
// Here you should be writing your db logic to save it.
dummyDb.subscription = subscription;
};
// The new /save-subscription endpoint
app.post("/save-subscription", async (req, res) => {
const subscription = req.body;
await saveToDatabase(subscription); //Method to save the subscription to Database
res.json({ message: "success" });
});
const vapidKeys = {
publicKey:
'BFPtpIVOcn2y25il322-bHQIqXXm-OACBtFLdo0EnzGfs-jIGXgAzjY6vNapPb4MM1Z1WuTBUo0wcIpQznLhVGM',
privateKey: 'mHSKS-uwqAiaiOgt4NMbzYUb7bseXydmKObi4v4bN6U',
}
webpush.setVapidDetails(
'mailto:janakprajapati90#email.com',
vapidKeys.publicKey,
vapidKeys.privateKey
)
const sendNotification = (subscription, dataToSend='') => {
webpush.sendNotification(subscription, dataToSend)
}
app.get('/send-notification', (req, res) => {
const subscription = {endpoint:"https://fcm.googleapis.com/fcm/send/dLjyDYvI8yo:APA91bErM4sn_wRIW6xCievhRZeJcIxTiH4r_oa58JG9PHUaHwX7hQlhMqp32xEKUrMFJpBTi14DeOlECrTsYduvHTTnb8lHVUv3DkS1FOT41hMK6zwMvlRvgWU_QDDS_GBYIMRbzjhg",expirationTime:null,keys:{"p256dh":"BE6kUQ4WTx6v8H-wtChgKAxh3hTiZhpfi4DqACBgNRoJHt44XymOWFkQTvRPnS_S9kmcOoDSgOVD4Wo8qDQzsS0",auth:"CfO4rOsisyA6axdxeFgI_g"}} //get subscription from your databse here.
const message = 'Hello World'
sendNotification(subscription, message)
res.json({ message: 'message sent' })
})
app.listen(port, () => console.log(`Example app listening on port ${port}!`));
Please help me
Try the following code:
index.js
const check = () => {
if (!("serviceWorker" in navigator)) {
throw new Error("No Service Worker support!");
} else {
console.log("service worker supported")
}
if (!("PushManager" in window)) {
throw new Error("No Push API Support!");
} else {
console.log("PushManager worker supported")
}
};
const saveSubscription = async subscription => {
console.log("Save Sub")
const SERVER_URL = "http://localhost:4000/save-subscription";
const response = await fetch(SERVER_URL, {
method: "post",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(subscription)
});
return response.json();
};
const urlB64ToUint8Array = base64String => {
const padding = "=".repeat((4 - (base64String.length % 4)) % 4);
const base64 = (base64String + padding)
.replace(/\-/g, "+")
.replace(/_/g, "/");
const rawData = atob(base64);
const outputArray = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
};
const registerServiceWorker = async () => {
return navigator.serviceWorker.register("service.js?"+Math.random()).then((swRegistration) => {
console.log(swRegistration);
return swRegistration;
});
};
const requestNotificationPermission = async (swRegistration) => {
return window.Notification.requestPermission().then(() => {
const applicationServerKey = urlB64ToUint8Array(
"BFPtpIVOcn2y25il322-bHQIqXXm-OACBtFLdo0EnzGfs-jIGXgAzjY6vNapPb4MM1Z1WuTBUo0wcIpQznLhVGM"
);
const options = { applicationServerKey, userVisibleOnly: true };
return swRegistration.pushManager.subscribe(options).then((pushSubscription) => {
console.log(pushSubscription);
return pushSubscription;
});
});
};
const main = async () => {
check();
const swRegistration = await registerServiceWorker();
const subscription = await requestNotificationPermission(swRegistration);
// saveSubscription(subscription);
};
service.js
self.addEventListener("push", function(event) {
if (event.data) {
console.log("Push event!! ", event.data.text());
} else {
console.log("Push event but no data");
}
});
I can think of three reasons that the permission is denied
1) your site is not on https (including localhost that is not on https), the default behaviour from chrome as far as i know is to block notifications on http sites. If that's the case, click on the info icon near the url, then click on site settings, then change notifications to ask
2) if you are on Safari, then safari is using the deprecated interface of the Request permission, that is to say the value is not returned through the promise but through a callback so instead of
Notification.requestPermission().then(res => console.log(res))
it is
Notification.requestPermission(res => console.log(res))
3) Your browser settings are blocking the notifications request globally, to ensure that this is not your problem run the following code in the console (on a secured https site)
Notification.requestPermission().then(res => console.log(res))
if you receive the alert box then the problem is something else, if you don't then make sure that the browser is not blocking notifications requests