send a websocket message in a route, Express.js - javascript

The Goal:
Let's say we have a marketplace full of shops. I'm creating a specific page localhost:3000/livePurchases/:storeId for a shop owner to monitor live notifications of when they got a new purchase.
alert('you received a new purchase') should be triggered by a WebSocket when an item is purchased.
The Problem:
I need to set up WebSockets on my express server so that the websocket can be triggered somewhere in my code different from where the websocket server was set up. But I don't understand how to do this.
The route /newPurchase/:storeId would be requested by the browser of a customer after they successfully purchase an item. The websocket should send a message within the code of the route "/newPurchase/:storeId" (backend) to the websocket on "/livePurchases/:storeId" (frontend) where the shop owner can monitor live purchases.
app.js
const express = require("express");
module.exports = (config) => {
const app = express();
app.post("/newPurchase/:storeId", (req, res, next) => {
const { storeId } = req.params;
// trigger websocket message to `localhost:3000/livePurchases/:storeId`
// when client requests this route
});
return app;
};
But app.js is exported and run from another script, www.js. In real scenarios this is to connect a database before running the app.:
www.js
const app = require("../server/app")();
const port = process.env.PORT || "4000";
app.set("port", port);
app
.listen(port)
.on("listening", () =>
console.log("info", `HTTP server listening on port ${port}`)
);
module.exports = app;
So that means that the web socket server needs to be set up in www.js.
Below is a notifier service I got it from this tutorial, which seemed like it was trying to solve the problem I have, but it didn't explain how to implement it. It is a class that handles the websocket.
NotifierService.js
const url = require("url");
const { Server } = require("ws");
class NotifierService {
constructor() {
this.connections = new Map();
}
connect(server) {
this.server = new Server({ noServer: true });
this.interval = setInterval(this.checkAll.bind(this), 10000);
this.server.on("close", this.close.bind(this));
this.server.on("connection", this.add.bind(this));
server.on("upgrade", (request, socket, head) => {
console.log("ws upgrade");
const id = url.parse(request.url, true).query.storeId;
if (id) {
this.server.handleUpgrade(request, socket, head, (ws) =>
this.server.emit("connection", id, ws)
);
} else {
socket.destroy();
}
});
}
add(id, socket) {
console.log("ws add");
socket.isAlive = true;
socket.on("pong", () => (socket.isAlive = true));
socket.on("close", this.remove.bind(this, id));
this.connections.set(id, socket);
}
send(id, message) {
console.log("ws sending message");
const connection = this.connections.get(id);
connection.send(JSON.stringify(message));
}
broadcast(message) {
console.log("ws broadcast");
this.connections.forEach((connection) =>
connection.send(JSON.stringify(message))
);
}
isAlive(id) {
return !!this.connections.get(id);
}
checkAll() {
this.connections.forEach((connection) => {
if (!connection.isAlive) {
return connection.terminate();
}
connection.isAlive = false;
connection.ping("");
});
}
remove(id) {
this.connections.delete(id);
}
close() {
clearInterval(this.interval);
}
}
module.exports = NotifierService;
Where I left off implementing the `NotifierService`
I added the websocket server with the NotifierService in www.js
www.js with websockets added
const app = require("../server/app")();
const NotifierService = require("../server/NotifierService.js");
const notifier = new NotifierService();
const http = require("http");
const server = http.createServer(app);
notifier.connect(server);
const port = process.env.PORT || "4000";
app.set("port", port);
server
.listen(port)
.on("listening", () =>
console.log("info", `HTTP server listening on port ${port}`)
);
module.exports = app;
But now how do I send the websocket message from the /newPurchase route in app.js on the backend? If I create a new instance of NotifierService in app.js in order to use the notifierService.send method in the /newPurchase route, then the new NotifierService instance won't have access to the websocket connections because it would be a different instance than the one initiated on www.js.
Front End:
App.js
import React from "react";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import LiveStorePurchases from "./LiveStorePurchases";
function App(props) {
return (
<div className="App">
<Router>
<Switch>
<Route exact path="/livePurchases/:storeId">
<LiveStorePurchases />
</Route>
</Switch>
</Router>
</div>
);
}
export default App;
LivePurchaseServer.js
import React, { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
export default function LiveStorePurchases() {
let { storeId } = useParams();
const URL = "ws://127.0.0.1:4000?storeId=" + storeId;
const [ws, setWs] = useState(new WebSocket(URL));
useEffect(() => {
ws.onopen = (e) => {
newFunction(e);
function newFunction(e) {
alert("WebSocket Connected");
}
};
ws.onmessage = (e) => {
const message = e.data;
alert(message);
};
return () => {
ws.onclose = () => {
alert("WebSocket Disconnected");
setWs(new WebSocket(URL));
};
};
}, [ws.onmessage, ws.onopen, ws.onclose, ws, URL]);
return (
<div
style={{
color: "red",
fontSize: "4rem",
}}
>
store: {storeId}
</div>
);
}

app.js:
I was able to move the websocket instance to app.js instead of www.js. Then I simply passed that instance around to other routes.
const express = require("express");
const NotifierService = require("../server/NotifierService.js");
const notifier = new NotifierService();
const http = require("http");
const routes = require("./routes");
module.exports = (config) => {
const app = express();
const server = http.createServer(app); // websocket created
notifier.connect(server); // and connected here in app.js
// I moved POST /newPurchase to routes.js in order
// to demonstrate how the notifier instance can be
// passed around to different routes
app.use(routes(notifier));
return server;
};
routes.js:
I created a routes file routes.js to show that
you could move the notifier instance around and call it from any route.
const express = require("express");
const router = express.Router();
// I moved "/newPurchase/:id" to this routes.js file to show that
// I could move the notifier instance around.
module.exports = (webSocketNotifier) => {
router.post("/newPurchase/:id", (req, res, next) => {
webSocketNotifier.send(req.params.id, "purchase made");
res.status(200).send();
});
return router;
};
www.js:
const server = require("../server/app")();
const port = process.env.PORT || "4000";
server
.listen(port)
.on("listening", () =>
console.log("info", `HTTP server listening on port ${port}`)
);
module.exports = server;

Related

Unable to connect front end to back end while following a tutorial

I am following this tutorial https://www.youtube.com/watch?v=ZwFA3YMfkoc&ab_channel=JavaScriptMastery (github: https://github.com/adrianhajdin/project_chat_application) trying to create a real time chat app and I am unable to make a connection and I am unable to find what the issue is especially since my code looks exactly the same as the video.
import React, { useState, useEffect } from "react"; enter code hereimport queryString from "query-string"; import io from "socket.io-client";
import "./Chat.css";
let socket;
export default function Chat({ location }) { const [name, setName] = useState(""); const [room, setRoom] = useState(""); const ENDPOINT
= "localhost:5000"; console.log("logging!"); useEffect(() => {
const { name, room } = queryString.parse(location.search);
socket = io(ENDPOINT);
setRoom(room);
setName(name);
console.log(name,room)
socket.emit("join", { name, room }, (error) => {
if (error) {
alert(error);
}
}); }, [ENDPOINT, location.search]);
return <div>Hello World</div>; }
The console.log I put in isnt logging either and when I check the console this is the message I get repreatedly:
polling-xhr.js:202 GET http://localhost:5000/socket.io/?EIO=4&transport=polling&t=NSV19AI net::ERR_FAILED
The message I am expecting to get is an io object with "name" and "room" and also a "we have a new connection!!!" on the back end. This is the back end I am trying to connect to which as far as I've seen is working fine:
const express = require("express");
const socketio = require("socket.io");
const http = require("http");
const PORT = process.env.PORT || 5000;
const router = require("./router");
const { callbackify, isRegExp } = require("util");
const app = express();
const server = http.createServer(app);
const io = socketio(server);
io.on("connection", (socket) => {
console.log("we have a new connection!!!");
socket.on("join", ({ name, room }) => {
console.log(name, room);
const error = true;
if (error) {
callback({ error: "error" });
}
});
socket.on("disconnect", () => {
console.log("user disconnected :O!");
});
});
app.use(router);
server.listen(PORT, () => console.log(`server is running on port ${PORT}`));
I've been away from React and Express for a few months now so its likely there is something obvious I'm missing but when the useEffect console.log isnt logging anything I know something strange is happening.
This is the first time I will have posted a question on here so any feedback on how I can better phrase things is welcome.
You have defined,
const ENDPOINT= "localhost:5000";
It should be,
const ENDPOINT = "http://localhost:5000";

How can I test express server with supertest in next.js?

I have built my portfolio webpage with next.js now I need to test it. to test the express server I use supertest. But the problem is I need to refactor express to use it. Because supertest need to access to app() before listening.
I started the way how I used to implement in node.js app. Put the express code in app.js and call it in index.js.
const express = require("express");
const server = express();
const authService = require("./services/auth");
const bodyParser = require("body-parser");
//put all the middlewares here
module.exports = server;
and then in index.js
const server = require("express")();
// const { parse } = require("url");
const next = require("next");
const routes = require("../routes");
const path = require("path");
require("./mongodb");
const dev = process.env.NODE_ENV !== "production";
const app = next({ dev });
// const handle = app.getRequestHandler(); //this is built in next route handler
const handle = routes.getRequestHandler(app);
app
.prepare()
.then(() => {
const server = require("./app");
//I required this outside too but it did not solve the issue
server.listen(3000, (err) => {
if (err) throw err;
console.log("> Ready on http://localhost:3000");
});
})
.catch((ex) => {
console.error(ex.stack);
process.exit(1);
});
with this set up, express is listening, I am able connect to mongodb, during the start up there is no issue.
When i request to localhost:3000, there is no response from localhost, it is spinning till timeout
Create a test client:
// test-client.ts
import { createServer, RequestListener } from "http";
import { NextApiHandler } from "next";
import { apiResolver } from "next/dist/next-server/server/api-utils";
import request from "supertest";
export const testClient = (handler: NextApiHandler) => {
const listener: RequestListener = (req, res) => {
return apiResolver(
req,
res,
undefined,
handler,
{
previewModeEncryptionKey: "",
previewModeId: "",
previewModeSigningKey: "",
},
false
);
};
return request(createServer(listener));
};
Test your APIs with:
// user.test.ts
import viewerApiHandler from "../api/user";
import { testClient } from "../utils/test-client";
const request = testClient(viewerApiHandler);
describe("/user", () => {
it("should return current user", async () => {
const res = await request.get("/user");
expect(res.status).toBe(200);
expect(res.body).toStrictEqual({ name: "Jane Doe" });
});
});
For those who want to add query parameters, here's the answer:
import { createServer, RequestListener } from 'http'
import { NextApiHandler } from 'next'
import { apiResolver } from 'next/dist/server/api-utils/node'
import request from 'supertest'
export const handlerRequester = (handler: NextApiHandler) => {
const listener: RequestListener = (req, res) => {
let query = {}
let queryUrl = req.url.split('?')[1]
if (queryUrl) {
queryUrl
.split('&')
.map((p) => [p.split('=')[0], p.split('=')[1]])
.forEach((k) => {
query[k[0]] = k[1]
})
}
return apiResolver(
req,
res,
query,
handler,
{
previewModeEncryptionKey: '',
previewModeId: '',
previewModeSigningKey: '',
},
false
)
}
const server = createServer(listener)
return [request(server), server]
}
I've just released a new npm package which handle this case here:
https://www.npmjs.com/package/nextjs-http-supertest
Feel free to test it and give me feedback !

no response socket.io with reactjs

two info: [HMR] Waiting for update signal from WDS...
Download the React DevTools for a better development experience: fb.me/react-devtools
error: http://127.0.0.1:4001/socket.io/?EIO=3&transport=polling&t=N4qdmGu
Failed to load resource: net::ERR_CONNECTION_REFUSED what should be done to make this work?
const express = require("express");
const http = require("http");
const socketIo = require("socket.io");
const axios = require("axios");
const port = process.env.PORT || 4001;
const index = require("./routes/index");
const app = express();
app.use(index);
const server = http.createServer(app);
const io = socketIo(server);
let interval;
io.on("connection", socket => {
console.log("New client connected");
if (interval) {
clearInterval(interval);
}
interval = setInterval(() => getApiAndEmit(socket), 10000);
socket.on("disconnect", () => {
console.log("Client disconnected");
});
});
const getApiAndEmit = async socket => {
try {
const res = await axios.get(
"https://api.darksky.net/forecast/db057094f57ede5e1f8d33d5e528e4b3/30.9871097,34.9408864"
);
let temp = (((res.data.currently.temperature - 32) * 5) / 9).toFixed(2);
socket.emit("FromAPI", temp);
} catch (error) {
console.error(`Error: ${error.code}`);
}
};
server.listen(port, () => console.log(`Wee wee i'm on port ${port}`));
than
import React, { Component } from "react";
import socketIOClient from "socket.io-client";
class App extends Component {
constructor() {
super();
this.state = {
response: false,
endpoint: "http://127.0.0.1:4001"
};
}
componentDidMount() {
const socket = socketIOClient(this.state.endpoint);
socket.on("FromAPI", data => this.setState({ response: data }));
}
render() {
const { response } = this.state;
return (
<div style={{ textAlign: "center" }}>
{
response ?
(<p>The temperature is {response} degrees</p>)
:
(<p>load load...</p>)
}
</div>
);
}
}
export default App;
You have to first connect to the socket server on the client side so for that add this
socket.on('connect', function(){});
before your event listener.
socket.on("FromAPI", data => this.setState({ response: data }));

Why no response socket.io with react.js

server side:
const express = require("express");
const http = require("http");
const socketIo = require("socket.io");
const axios = require("axios");
const port = process.env.PORT || 4001;
const index = require("./routes/index");
const app = express();
app.use(index);
const server = http.createServer(app);
const io = socketIo(server);
let interval;
io.on("connection", socket => {
console.log("New client connected");
if (interval) {
clearInterval(interval);
}
interval = setInterval(() => getApiAndEmit(socket), 10000);
socket.on("disconnect", () => {
console.log("Client disconnected");
});
});
const getApiAndEmit = async socket => {
try {
const res = await axios.get(
"https://api.darksky.net/forecast/db057094f57ede5e1f8d33d5e528e4b3/30.9871097,34.9408864"
);
let temp = (((res.data.currently.temperature - 32) * 5) / 9).toFixed(2);
socket.emit("FromAPI", temp);
} catch (error) {
console.error(`Error: ${error.code}`);
}
};
server.listen(port, () => console.log(`Wee wee i'm on port ${port}`));
client side:
import React, { Component } from "react";
import socketIOClient from "socket.io-client";
class App extends Component {
constructor() {
super();
this.state = {
response: false,
endpoint: "http://127.0.0.1:4001"
};
}
componentDidMount() {
const socket = socketIOClient(this.state.endpoint);
socket.on("FromAPI", data => this.setState({ response: data }));
}
render() {
const { response } = this.state;
return (
<div style={{ textAlign: "center" }}>
{
response ?
(<p>The temperature is {response} degrees</p>)
:
(<p>load load...</p>)
}
</div>
);
}
}
export default App;
2 info:
[HMR] Waiting for update signal from WDS...
Download the React DevTools for a better development experience: fb.me/react-devtools
1 error:
http://127.0.0.1:4001/socket.io/?EIO=3&transport=polling&t=N4qdmGu
Failed to load resource: net::ERR_CONNECTION_REFUSED
What's wrong?
You should try to set endpoint in client side to 'http://localhost:4001'.

Socket connection in a API route controller retains the data of previous call

I have an API endpoint in my Node/Express app. The endpoint is responsible to upload files. There are several stages involved in the upload process. like image conversion, sending images to another third party API, etc. I am using socket.io to tell the client about the current stage of upload.
The problem is, The socket connection works fine in the first call, but in my second call to the endpoint, the socket connection runs twice and retains the data which I sent in the previous call.
Here's my code:
server.js
import ClientsRouter from './api/routes/clients';
import express from 'express';
import http from 'http';
import io from 'socket.io';
const port = process.env.PORT || 3000;
const app = express();
const server = http.Server(app);
const socket = io(server);
app.set('socket', socket);
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.set('view engine', 'ejs');
app.use(express.static('dist'))
app.use('/uploads', express.static('uploads'));
app.use('/api/clients', ClientsRouter);
server.listen(port, () => console.log(`Server Listening on ${process.env.URL}`));
api/routes/clients.js
import express from 'express';
import ClientsController from '../controllers/clients';
ClientsRouter.post('/uploadClientData/', clientDataUpload.array('client_data'), ClientsController.uploadClientData);
controllers/clients.js
const uploadClientData = async (req, res) => {
try {
const files = req.files
const clientFolder = req.body.client_folder
const { dbxUser } = req;
const team_member_id = req.body.team_member_id;
const data = req.files.map( i => ({team_member_id, destination: i.destination.substring(1), filename: i.filename, status: 1 } ))
const io = req.app.get("socket");
console.log("Outside Socket", data); //This contains my currently passed data
io.on('connection', async socket => {
console.log("Socket Connection established");
console.log("Inside Socket", data); //But This contains my current data aling with the data that I passed in previous call
await uploadQueue.collection.insertMany(data)
socket.emit('upload stage', { upload_stage: 2, progress: 33 })
await helpers.convertImagesToWebResolution(team_member_id, req.body.dpi, req.body.resolution);
socket.emit('upload stage', { upload_stage: 3, progress: 66 })
await helpers.uploadImagesToDropbox(team_member_id, dbxUser, clientFolder)
socket.emit('upload stage', { upload_stage: 4, progress: 100 })
})
res.status(200).json({message: "Uploaded"});
} catch (error) {
console.log(error)
res.status(500).json({
error
});
}
}
And in my front-end react component
componentDidMount(){
const { currentFolder } = this.props;
this.setState({ client_folder: currentFolder }, () => this.afterFileSelect())
}
componentDidUpdate(prevProps){
const { selectedFiles } = this.props;
if(prevProps.selectedFiles !== selectedFiles){
this.afterFileSelect()
}
}
afterFileSelect = async () => {
const { selectedFiles, setSelectedFiles, currentFolder, user, uploadSettings} = this.props;
let formData = new FormData()
formData.append('client_folder', currentFolder)
formData.append('team_member_id', user.team_member_id)
formData.append('resolution', uploadSettings.resolution.split("x")[0])
formData.append('dpi', uploadSettings.dpi)
for(let selectedFile of selectedFiles){
formData.append('client_data', selectedFile)
}
let uploadResp = uploadSettings.convert_web_res ? await uploadClientData(formData) : await dropboxDirectUpload(formData)
const endpoint = uploadResp.config.url;
const host = endpoint.substring(0, endpoint.indexOf("api"));
const socket = socketIOClient(host);
socket.on("upload stage", data => {
this.setState({upload_stage: data.upload_stage, progress: data.progress})
data.upload_stage === 4 && this.setState({client_folder: ""})
})
}
Also I want to know if this is the correct way to to track upload progress?

Categories

Resources