WebSockets via browser on React + NodeJS - javascript

I'm building a websocket chat service, running on NodeJS. It's already working on terminal, if you want to try, check the terminal client package. https://www.npmjs.com/package/#redstone-solutions/hackerchat-client
It works fine on the terminal, now i'm developing a package to integrate web apps (javascript, react, etc), but i can't connect to the websocket via browser.
Basically, that's the backend on the websocket creation:
(I'm not using socket.io, the only dependency is uuid)
async initialize(eventEmitter: NodeJS.EventEmitter): Promise<http.Server> {
const server = http.createServer((request: http.IncomingMessage, response: http.ServerResponse) => {
response.setHeader('Access-Control-Allow-Origin', '*');
response.setHeader('Access-Control-Request-Method', '*');
response.setHeader('Access-Control-Allow-Methods', 'OPTIONS, GET');
response.setHeader('Access-Control-Allow-Headers', '*');
response.writeHead(200, { 'Content-Type': 'text/plain' })
response.end('Hacker chat server is running!\n\nPlease connect with websocket protocol.')
})
server.on('upgrade', (request, socket) => {
socket.id = uuid()
const headers = [
'HTTP/1.1 101 Web Socket Protocol Handshake',
'Upgrade: WebSocket',
'Connection: Upgrade',
''
]
.map(line => line.concat('\r\n'))
.join('')
socket.write(headers)
eventEmitter.emit(EventTypes.event.NEW_USER_CONNECTED, socket)
})
return new Promise((resolve, reject) => {
server.on('error', reject)
server.listen(this.port, () => resolve(server))
})
}
Here's my try on a new react app, i'm using the same ways that i use on the terminal client.
function App() {
useEffect(() => {
async function run() {
const componentEmitter = new Events()
const connection = await createConnection()
console.log(connection)
}
run()
}, [])
async function createConnection() {
const options = {
port: '9898',
host: 'localhost',
headers: {
Connection: 'Upgrade',
Upgrade: 'websocket'
}
}
const request = http.request(options)
request.end()
return new Promise(resolve => {
request.once('upgrade', (response, socket) => resolve(socket))
})
}
return <div />
}
The request never upgrades on the react app! The console.log(connection) is never called.
I'm not sure if i'm using a resource that's not compatible with browsers (http package, for example)...
The applications are very simple/small, if you need the full code:
https://github.com/redstone-solutions/hackerchat-server
https://github.com/redstone-solutions/hackerchat-terminal-client
Any idea?

I found a way to connect with the socket, but I will need to restructure my application.
const server = new WebSocket('ws://localhost:9898')
Using the native WebSocket API from JavaScript, i can stablish a connection.
With the http library, the request never upgrades, i believe that this lib doesn't fully work on the browser.

Related

How to mock a Kubernetes API endpoint that returns a socket?

I am mocking some endpoints of the Kubernetes API in a mock server for some integration tests and got stuck in the implementation of the endpoint /apis/batch/v1/watch/namespaces/{namespace}/jobs?watch=true (doc, need to search for batch/v1/watch in the page). The client uses this method to make a GET request and keep the connection open to receive events related to Job resources. Apparently, it handles a 'socket' event.
I implemented a simple mock endpoint that returns the data I want, but I get this error when the request is made:
Error: socket hang up
at connResetException (node:internal/errors:691:14)
at Socket.socketOnEnd (node:_http_client:466:23)
at Socket.emit (node:events:538:35)
at endReadableNT (node:internal/streams/readable:1345:12)
at processTicksAndRejections (node:internal/process/task_queues:83:21)
How should this mock be implemented? Do I need to return a socket? How should I do that?
Answering to my own question: after some attempts, all I needed to do was to use res.write() with a string that ends with a line break character, like this:
import express from 'express';
const TIME_BETWEEN_EVENTS = 500; // Milliseconds
const app = express();
app.get('/apis/batch/v1/watch/namespaces/:namespace/jobs', (_, res) => {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
Connection: 'keep-alive',
});
console.log('Client connected to Jobs watch endpoint');
let jobEventsSent = 0;
const interval = setInterval(() => {
if (jobEventsSent < 3) {
console.log('Sending data to Jobs watcher');
res.write(
Buffer.from(JSON.stringify({ type: 'ADDED', object: { /* Job Resource */ } }) + '\n'),
);
jobEventsSent++;
} else {
clearInterval(interval)
}
}, TIME_BETWEEN_EVENTS);
res.socket.on('close', () => {
console.log('Client disconnected from Jobs watch endpoint');
if (interval) clearInterval(interval);
});
});
The code above keeps the connection alive event after all the messages have been sent. To close the connection, it is necessary to call res.end();

can't connect to websocket (Mixed Content)

so i was trying to make web socket server
node js
const WebSocket = require("ws");
var porth = process.env.PORT || 80;
const wss = new WebSocket.Server({port:porth})
wss.on("connection", ws => {
console.log("nowe połączenie");
ws.on("message", data => {
console.log(data.toString());
});
ws.send("hej");
ws.on("close", () =>
{
console.log("rozłączenie");
});
})
app.js
ws.addEventListener("open", () => {
console.log("połączono")
ws.send("test");
ws.addEventListener("message", data => {
console.log(data.data)
})
})
and when i host it on my pc it works but
when i upload it to github pages it keeps
sending me error:
Mixed Content: The page at 'https://*****.github.io/' was loaded over HTTPS, but attempted to connect to the insecure WebSocket endpoint 'ws://****.herokuapp.com/'. This request has been blocked; this endpoint must be available over WSS.
and i dont know what should i do about it
So from this error I assume that you are connecting between secure and not secure site. I'm not sure but if I can I would suggest you to see ws https server. Maybe this will help https://github.com/websockets/ws#external-https-server

Deploy Node.js socket.io project on shared OVH server

I'm trying to build and deploy my app to OVH server, my client doesn't want a cloud solution, he want to host it or deploy it on OVH (they told me OVH support Node.js) and to be honest i have no idea how to do it.
my project work fine in development, its a Real-time chat with socket.io and MySql and some package as knex, in front-end i worked with React.js ( which i have no problem with it right now )
I can provide more informations if needed. thx a lot
const app = require("express")();
var cors = require("cors");
app.use(cors());
const server = require("http").createServer(app);
const mysql = require("mysql");
const knex = require("knex")({
client: "mysql",
connection: {
host: "localhost",
user: "root",
password: "",
database: "chat_message",
},
});
const io = require("socket.io")(server, {
cors: {
origin: "*",
credentials: true,
},
});
app.get("/messages", function (request, result) {
knex
.select()
.table("messages")
.then((data) => result.send(data))
});
io.on("connection", (socket) => {
socket.on("messageClient", (sms) => {
knex("messages")
.insert({
message: sms.msg,
socket_id: sms.id,
dateMsg: sms.Temps,
ip: sms.ip,
name: sms.name,
})
.then((e) => console.log("data insert succees"));
socket.broadcast.emit("messageAll", sms);
});
});
server.listen(5000, () => console.log("Port: 5000"));
OVH is a private company and I'm not sure if this would be offtopic and more suitable to ask their own support. However you should know that shared hosting in general does not support long running processes like nodejs. They only support PHP on the server.

Websocket connection error: returns 101, but does not upgrade

I am setting up some websockets using ws library. I am struggling to set up authorisation using a handshake. I have added a route to our server to upgrade to a websocket connection like so:
.get(
'/chat',
authorisationFunction,
upgradeConnection,
),
The websocket server:
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 3030 });
This is the upgradeConnection function, which will run if authorisation is successful:
const upgradeConnection = (request, socket, head) => {
return wss.handleUpgrade(request, request.socket, head, function done(ws) {
return wss.emit('connection', ws, request);
});
}
I also have a function that listens to messages:
function webSocketsServer() {
wss.on('connection', (ws, request, client) => {
ws.on('message', message => {
ws.send(message);
});
});
}
A connection gets emitted, and from my server I get this response:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: QyVvqadEcI1+ALka6j2pLKBkfNQ=
but then immediately on my client I get the error “WebSocket connection to 'ws://localhost:3000/chat’ failed: Invalid frame header”.
But when I bypass the handshake and connect directly to my websocket server, I can send messages successfully. The error is only on the client and not on the backend. What am I missing?
I am not 100% sure it is the only way but might help so I post it.
Based on this answer I would go for a server that uses the same port for http and websocket connections.
You can achieve it like this:
const { createServer } = require('http')
const ws = require('ws')
const express = require('express')
const app = express()
const server = createServer(app)
app.get('/', (req, res) => {
res.send('I am a normal http server response')
})
const wsServer = new ws.Server({
server,
path: '/websocket-path',
})
wsServer.on('connection', (connection) => {
connection.send('I am a websocket response')
})
server.listen(3030, () => {
console.log(`Server is now running on http://localhost:3030`)
console.log(`Websocket is now running on ws://localhost:3030/<websocket-path>`)
})
So your server listens on port 3030 for normal http requests. If it gets a websocket connection request on path '/websocket-path' it is passed to the the ws connection handler and from there you are good to go.

How to Send Streamed UDP Packets to Browser Using SSE in Node JS?

I have a node js server that listens to audio that is streamed on a local network using VBAN protocol. VBAN, basically a protocol that sends audio stream over local network using UDP. You can read more about VBAN and it's applications here
The next step, after receiving the audio from the VBAN, the node js server process the received audio.
Also, the node js server should send the processed packages to the browser, in this case, using EventEmitter, through a GET request:
const EventEmmitter = require("events");
const stream = new EventEmmitter();
app.get("/stream", function (req, res) {
res.writeHead(200, {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
});
stream.on("push", function (event, data) {
res.write(`event: ${event}, data: ${JSON.stringify(data)}`);
});
});
In the front-end side, which is an Angular application, receives the data from the server using EventSource
public dataObservable(): Observable<MessageEvent> {
return Observable.create((observer) => {
let eventSource = new EventSource('http://localhost:3000/stream');
eventSource.onmessage = (event) => {
console.log('Received event: ', event);
const json = JSON.parse(event.data);
console.log("audio", json);
observer.next(json);
};
eventSource.onerror = (error) => {
if (eventSource.readyState === 0) {
console.log('The stream has been closed by the server.');
eventSource.close();
observer.complete();
} else {
observer.error('EventSource error: ' + error);
}
};
});
}
From the inspector, in the Network, it shows that the browser is receiving the data from the server, but there is no data being emitted from the EventSource.
I'm not sure whether I'm using the wrong approach or I have the wrong implementation of this approach. I would appreciate your help.
I was able to send the processed packages to the front-end side using Socket IO.
Here's the server-side implementation (node js):
first of all, I installed socket.io dependency into node js app:
npm install socket.io
Then, to initialize the socket.io:
var express = require("express");
var app = express();
const http = require("http").createServer(app);
const io = require("socket.io")(http);
And here I've configured the HTTP get request, and make the app listing to the specified port:
app.use(function (req, res, next) {
res.header("Access-Control-Allow-Origin", "http://localhost:4200");
res.header("Access-Control-Allow-Credentials", true);
res.header(
"Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept"
);
next();
});
app.get("/", (req, res) => {
res.send("<h1>Hey Socket.io</h1>");
});
http.listen(3000, () => {
console.log("Server running on port 3000");
});
Next, emitting the audio messages using the socket.io
io.on("connection", (socket) => {
console.log("a user connected");
socket.on("disconnect", () => {
console.log("user disconnected");
});
});
io.on("connection", () => {
let dgram = require("dgram");
let server = dgram.createSocket("udp4");
server.on("listening", () => {
let address = server.address();
console.log("server host", address.address);
console.log("server port", address.port);
});
server.on("message", function (message, remote) {
let audioData = vban.ProcessPacket(message);
io.emit("audio", audioData); // // console.log(`Received packet: ${remote.address}:${remote.port}`)
});
server.bind(PORT, HOST);
});
In the front-end (Angular):
I've added socket.io dependency into the Angular app:
npm install socket.io-client
Finally, I've added a listener for my audio event:
export const environment = {
production: false,
SOCKET_ENDPOINT: 'http://localhost:3000'
};
setupSocketConnection() {
var socket = io(environment.SOCKET_ENDPOINT);
socket.emit('my message', 'Hello there from Angular.');
socket.on('audio', (audio) => {
console.log('audio:' ,audio);
this.playAudio(audio);
});
}
Reference to the source which I've used to implement the socket.io
I still want to know if I can use EventEmitter to send the data to the client app.

Categories

Resources