index.ts
import { Server } from "./server";
const server = new Server();
server.listen(port => {
console.log(`Server is listening on http://localhost:${port}`);
});
server.ts
import express, { Application } from "express";
import { Server as SocketIOServer } from "socket.io";
// import { Server as HTTPServer } from "http";
import { Server as HTTPSServer } from "https";
import path from "path";
// for https
const https = require('https');
const fs = require('fs');
export class Server {
private httpServer: HTTPSServer;
private app: Application;
private io: SocketIOServer;
// private activeSockets: string[] = [];
private activeSockets = {};
private readonly DEFAULT_PORT = 5000;
constructor() {
this.initialize();
}
private initialize(): void {
this.app = express();
// this.httpServer = createServer(this.app);
this.app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});
// https
const options = {
key: fs.readFileSync('key.pem'),
cert: fs.readFileSync('cert.pem')
};
this.httpServer = https.createServer(options, this.app);
this.io = require("socket.io")(this.httpServer, {
handlePreflightRequest: (req, res) => {
const headers = {
"Access-Control-Allow-Headers": "Content-Type, Authorization",
"Access-Control-Allow-Origin": req.headers.origin, //or the specific origin you want to give access to,
"Access-Control-Allow-Credentials": true
};
res.writeHead(200, headers);
res.end();
}
});
//this.io.origins('*:*');
this.configureApp();
this.configureRoutes();
this.handleSocketConnection();
}
private configureApp(): void {
this.app.use(express.static(path.join(__dirname, "../public")));
}
private configureRoutes(): void {
this.app.get("/", (req, res) => {
res.sendFile("index.html");
});
this.app.use(function (req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
res.header("Access-Control-Allow-Methods", "*");
next();
});
}
private handleSocketConnection(): void {
this.io.on("connection", socket => {
var client_name = socket.handshake.query.name;
const existingSocket = this.activeSockets.hasOwnProperty(socket.id);
console.log("query parameter:")
console.log(client_name);
if (!existingSocket) {
// add the socket id to active socket list
this.activeSockets[socket.id] = { 'bot_id': client_name, 'service_status': 0 };
console.log("Active sockets" + JSON.stringify(this.activeSockets));
// send the u[dated list to all connected users
socket.emit("update-user-list", {
users: this.activeSockets
});
socket.broadcast.emit("update-user-list", {
// users: [socket.id]
users: this.activeSockets
});
}
socket.on("call-user", (data: any) => {
socket.to(data.to).emit("call-made", {
offer: data.offer,
socket: socket.id
});
});
socket.on("candidate", (data: any) => {
socket.to(data.to).emit("candidate", {
candidate: data.candidate,
socket: socket.id
});
});
socket.on("make-answer", data => {
socket.to(data.to).emit("answer-made", {
socket: socket.id,
answer: data.answer
});
});
socket.on("reject-call", data => {
socket.to(data.from).emit("call-rejected", {
socket: socket.id
});
});
socket.on("disconnect", () => {
// this.activeSockets = this.activeSockets.filter(
// existingSocket => existingSocket !== socket.id
// );
console.log("deleteing socket: ");
console.log(socket.id);
delete this.activeSockets[socket.id];
socket.broadcast.emit("remove-user", {
socketId: socket.id
});
});
socket.on("sdp", data => {
console.log("in sdp");
console.log(data);
socket.to(data.to).emit("sdp", {
sdp: data.sdp
});
});
});
}
public listen(callback: (port: number) => void): void {
this.httpServer.listen(this.DEFAULT_PORT, () => {
callback(this.DEFAULT_PORT);
});
}
}
This script exports a Server class that creates an HTTPS server using the https module, and sets up an Express application, a Socket.IO server, and some routes and handlers.
On instantiation, the Server class calls the initialize method which sets up an Express application, creates an HTTPS server using the https module and passes the application as a callback, creates a Socket.IO server by passing the HTTPS server as an argument, sets up some CORS headers, calls the configureApp and configureRoutes methods, and handleSocketConnection method.
The configureApp method sets up the Express app to serve static files from a public directory, and the configureRoutes method sets up a route for the root URL that sends the index.html file and sets CORS headers.
The handleSocketConnection method sets up event handlers for the connection, call-user, and make-answer events. It also uses the activeSockets object to keep track of connected clients, and emits updates to all connected clients when a new client connects or disconnects.
Related
I have my backend in NodeJS and frontend in NextJS. As soon I try to connect with the server for a WebSocket connection, the server connects the socket and immediately disconnects it.
This is what I get on my server as soon as I reload my frontend page:
user connected ---- OKGG9r0qvvBs0EWeAAAB test#test.com socket disconnected--- OKGG9r0qvvBs0EWeAAAB test#test.com
This is my server code:
import express from "express";
import dotenv from "dotenv";
import http from "http";
import { Server, Socket } from "socket.io";
import userRoutes from "./routes/users";
import { DefaultEventsMap } from "socket.io/dist/typed-events";
dotenv.config();
const PORT = process.env.PORT || 5000;
const app = express();
const server = http.createServer(app);
const io = new Server(server, {
cors: {
origin: process.env.CLIENT_URL,
},
});
app.use(express.json());
app.get("/ping", (req, res) => res.status(200).send("Pong"));
app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", "http://localhost:3000");
res.header("Access-Control-Allow-Credentials", "true");
res.header("Access-Control-Allow-Methods", "POST, GET");
res.header(
"Access-Control-Allow-Headers",
"Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With"
);
next();
});
interface SType extends Socket {
email?: string;
}
io.use((socket: SType, next) => {
const userEmail = socket.handshake.auth.email;
if (!userEmail) {
return next(new Error("Invalid email"));
}
console.log("userEmail-----", userEmail);
socket.email = userEmail;
next();
});
io.on("connection", (socket) => {
const users = [];
for (let [id, socket] of io.of("/").sockets) {
users.push({
userID: id,
email: (socket as any).email,
});
socket.emit("users", users);
}
socket.broadcast.emit("user connected", {
userID: socket.id,
email: (socket as any).email,
});
console.log("user connected ---- ", socket.id, (socket as any).email);
socket.on("disconnect", () => {
console.log("socket disconnected---", socket.id, (socket as any).email);
(socket as any).email = "";
});
});
app.use("/users", userRoutes);
server.listen(PORT, () => console.log(`server running on port ${PORT}`));
this is code on client side
useEffect(() => {
if (userState) {
socket.auth = { email: userState.email };
}
}, [userState]);
useEffect(() => {
socket.on("users", (users: any) => {
console.log("users", users);
setUsers(users);
});
socket.on("user connected", (user: any) => {
setUsers((previousState: any) => [...previousState, user]);
});
}, [socket]);
I tried the solutions from stack overflow as well as socket.io documentation, but nothing seems to work. Does anyone know what is happening here?
In Nodejs how to export socket IO in controller.
Socket.io Version - "socket.io": "^4.5.1",
Socket.js
let io = null;
// module.exports = {
// intialized_connection: (httpServer) => {
// return (io = require('socket.io')(httpServer, {
// cors: {
// origin: '*',
// methods: ['GET', 'POST', 'PUT', 'DELETE'],
// },
// }));
// },
// getIO: () => {
// if (!io) {
// throw new Error('Socket.io is not initialized');
// }
// return io;
// }
// }
class RealTime {
constructor() {
if (io) return io;
io = this;
return io;
}
intialized_connection(httpServer) {
return (io = require('socket.io')(httpServer, {
cors: {
origin: '*',
methods: ['GET', 'POST', 'PUT', 'DELETE'],
},
}));
}
init() {
io.on("connection", function (socket) {
console.log("A user connected", socket.id);
//Whenever someone disconnects this piece of code executed
// socket.on('custom-event', function(data) {
// console.log("Atique data: ", JSON.stringify(data));
// });
// socket.emit('custom-emit', "hello from nodejs")
socket.on('disconnect', function () {
console.log('A user disconnected');
});
});
}
getIO() {
if (!io) {
throw new Error('Socket.io is not initialized');
}
return io;
}
sendEvents(event, data) {
console.log("This.Socket:", this.socket);
return new Promise((resolve, reject) => {
this.getIO().emit(event, data, (response) => {
if (response.error) {
console.error(response.error);
reject(response.error);
} else {
resolve(true);
}
});
});
}
receivedEvents(event) {
console.log("Atique Ahmed Received Events ---->", event);
return new Promise((resolve, reject) => {
this.getIO().on(event, function(err, data) {
console.log("I am emiiting here, ")
if(err) {
reject(err);
}
resolve(data);
});
})
}
}
module.exports = {
RealTime
};
index.js
const express = require('express');
const bodyparser = require('body-parser');
const cors = require('cors');
const fileUpload = require('express-fileupload');
const http = require('http');
// const socketIO = require('./utils/socket');
const { RealTime } = require('./utils/socket');
const socket = new RealTime();
const app = express();
app.use(cors())
app.options('*', cors());
app.use(bodyparser.json({limit: '5mb', extended: true}))
app.use(bodyparser.urlencoded({limit: '5mb', extended: true}))
const authRoutes = require('./routes/authRoutes');
const apiRoutes = require('./routes/routes');
// For File Upload
app.use(fileUpload({
limits: { fileSize: 5 * 1024 * 1024 },
}));
app.use('/auth', authRoutes);
app.use('/user', apiRoutes);
//Capture All 404 errors
app.use(function (req,res,next){
res.status(404).send('Error - Unable to find the requested resource!');
});
app.use((req, res, next) => {
req.socket.on('error', () => {});
next();
});
const server = http.createServer(app);
socket.intialized_connection(server);
socket.init();
app.set('socketio', socket);//here you export my socket.io to a global
module.exports = server;
local.js
require('dotenv').config()
const server = require('./index');
const port = process.env.PORT || 8081;
const chalk = require('chalk');
// Server
server.listen(port, () => {
console.log(chalk.green('╔═══════════════════════════════════════════════════════════'));
console.log(chalk.green('║ Background Server Listening at | port: %s', port));
console.log(chalk.green('╚═══════════════════════════════════════════════════════════'));
});
Routes.js
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
app.use(bodyParser.json());
// Authentication
const authentication = require('../authentication');
// Middleware
const middleware = require('../middleware/headerValidation');
// Permission
const permissions = require('../permission/index')
// Controller
const userController = require('../controllers/userController');
const customerController = require('../controllers/customerController');
app.post('/submit-request', [middleware.bearerTokenPresent, authentication.verifyToken, permissions.fileUploadPermission], (req, res) => {
customerController.submitRequest(req, res);
});
module.exports = app;
customerController.js
exports.submitRequest = async(req, res) => {
const io = req.app.get('socketio');
io.emit('custom-emit', "Hello from nodejs");
io.on('custom-event', function(data) {
console.log("Atique:", JSON.stringify(data))
})
res.json("done")
}
Issue are -
The main issues are - socketio.on an socketio.emit is not working. It is working when I am putting everything, inside init method, I can't do that, I have to write the generic code, so it can be re-usable. -
init() {
io.on("connection", function (socket) {
console.log("A user connected", socket.id);
socket.on('custom-event', function(data) {
console.log("Atique data: ", JSON.stringify(data));
});
socket.emit('custom-emit', "hello from nodejs")
socket.on('disconnect', function () {
console.log('A user disconnected');
});
});
}
Frontend Angular 14, I am using, socket.io-client
Any idea, What I am doing wrong?
The object stored in your globals
app.set('socketio', socket);//here you export my socket.io to a global
is an instance of RealTime class and not of the require('socket.io').Server class.
please cache the reference to the proper object like so:
app.set('socketio',socket.intialized_connection(server));//here you export my socket.io to a global
socket.init();
change customerController.js:
exports.submitRequest = async (req, res) => {
const io = req.app.get('socketio');
///edited from io.on("connection", function (socket) {
io.once("connection", function (socket) {
socket.emit('custom-emit', "Hello from nodejs");
socket.on('custom-event', function (data) {
console.log("Atique:", JSON.stringify(data))
})
res.json("done")
});
}
you however have to keep in mind what listeners you are adding to the io object's "connection" or some other event as No checks are made to see if the listener has already been added. Multiple calls passing the same combination of "connection" and listener will result in the listener being added, and called, multiple times.
Consider using named functions and clearing the listener using removeListener() from time to time.
It is best to keep all your socket event listeners in one file for ease of debugging.
EDIT 1
index.js:
const server = http.createServer(app);
socket.intialized_connection(server);
socket.init();
app.set('socketio', socket);//here you export my socket.io to a global
make the following changes to your socket.js:
let io = null;
/// CHANGE:
let socketID = null;
class RealTime {
constructor() {
if (io) return io;
io = this;
return io;
}
intialized_connection(httpServer) {
return (io = require('socket.io')(httpServer, {
cors: {
origin: '*',
methods: ['GET', 'POST', 'PUT', 'DELETE'],
},
}));
}
init() {
io.on("connection", function (socket) {
console.log("A user connected", socket.id);
/// CHANGE:
socketID = socket.id
//Whenever someone disconnects this piece of code executed
// socket.on('custom-event', function(data) {
// console.log("Atique data: ", JSON.stringify(data));
// });
// socket.emit('custom-emit', "hello from nodejs")
socket.on('disconnect', function () {
console.log('A user disconnected');
});
});
}
/// CHANGE: getIO() {
getSocket() {
if (!io) {
throw new Error('Socket.io is not initialized');
}
return io.sockets.sockets.get(socketID);
}
.
.
.
.
for the receivedEvents and sendEvents to get reference to the socket.
Since you are caching the socket id here, this code will work only for one client properly.
change customerController.js:
exports.submitRequest = async (req, res) => {
const socket = req.app.get('socketio').getSocket();
socket.emit('custom-emit', "Hello from nodejs");
socket.on('custom-event', function (data) {
console.log("Atique:", JSON.stringify(data))
})
res.json("done")
}
When ever I run my server locally it works perfectly
But once I upload it to vercel I get errors like polling-xhr.js:202 GET https://giphy-chat-server.vercel.app/socket.io/?EIO=4&transport=polling&t=NQ03j3c&sid=H_PHDh9-4UKRVGTVAAAC 400
And WebSocket connection to 'wss://giphy-chat-server.vercel.app/socket.io/?EIO=4&transport=websocket&sid=k-Sex1ZKmrQQFoSKAAAA' failed: Error during WebSocket handshake: Unexpected response code: 400
I have tried so many solutions but none is working... I can't just figure out the problem. I would be glad if Its answered. Thank you
const express = require("express");
const app = express();
const http = require("http");
const path = require("path");
var server = http.createServer(app);
const io = require("socket.io")(server, {
cors: {
origin: "*",
credentials: true,
methods: ["GET", "POST"],
},
});
const { MONGODB_URI } = require("./config");
const port = process.env.PORT || 8000;
const Message = require("./message_model");
const mongoose = require("mongoose");
mongoose
.connect(MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
useFindAndModify: false,
})
.then((result) => {
server.listen(port, () => {
console.log(`Listening on port ${port}...`);
});
})
.catch((err) => {
console.log(err);
});
app.use(express.static(path.join(__dirname, "..", "client", "build")));
const users = [];
io.on("connection", (socket) => {
users.push({ id: socket.id });
io.emit("users", { users: users });
Message.find()
.sort({ createdAt: -1 })
.limit(10)
.exec((err, messages) => {
if (err) return console.error(err);
socket.emit("init", messages);
});
socket.on("message", (msg) => {
const message = new Message({
content: msg.content,
name: msg.name,
});
message.save((err) => {
if (err) return console.error(err);
});
socket.broadcast.emit("push", msg);
});
socket.on("disconnect", (reason) => {
let index = -1;
for (let i = 0; i < users.length; i++) {
const user = users[i];
if (user.id === socket.id) {
index = i;
}
}
if (index !== -1) {
users.splice(index, 1);
}
io.emit("users", { users: users });
});
});
app.get("/", (req, res) => {
res.send("Giphy Chat Server is running successfully");
});
app.use(function (req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "X-Requested-With");
res.header("Access-Control-Allow-Headers", "Content-Type");
res.header("Access-Control-Allow-Methods", "PUT, GET, POST, DELETE, OPTIONS");
next();
});
I think this will be due to Vercel's serverless functions having a maximum execution timeout, so they can't maintain a websocket connection. In order to use Websockets with Vercel you'll need to use a third party service to handle your websocket connections for you. Something like Ably or Pusher, or PubNub.
I just wrote up a demo of next + ably if it would be helpful - https://github.com/ably-labs/NextJS-chat-app
I'm trying to learn socket.io in my Angular/Node app.
I would emit an event in angular side then listen to it in Back end.
In my component file :
import { MatTableDataSource } from '#angular/material';
import { AjouttextService } from '../ajouttext.service';
import { DataSource } from "#angular/cdk/collections";
import { Observable } from "rxjs/Observable";
import { nomchamp } from '../model';
import * as io from "socket.io-client";
#Component({
selector: 'app-datatable',
templateUrl: './datatable.component.html',
styleUrls: ['./datatable.component.css']
})
export class DatatableComponent implements OnInit {
constructor(private dataService: AjouttextService) { }
data = [];
url = 'http://localhost:8080';
private socket = io.connect(this.url);
displayedColumns = ['text'];
dataSource = new UserDataSource(this.dataService);
;
applyFilter(filterValue: string) {
filterValue = filterValue.trim(); // Remove whitespace
filterValue = filterValue.toLowerCase(); // MatTableDataSource defaults to lowercase matches
}
ngOnInit() {
this.socket.emit('save-message', { room: "hello" });
}
}
Emit event in Angular is set OnInit so it should emit an event in all cases and my component is called by app.component.
Then In server side I would print a console.log on 'save-message' event
var http = require("http");
var admin = require('firebase-admin');
var firebase = require("firebase");
var express = require("express");
var app = express();
var bodyParser = require("body-parser");
var port = process.env.app_port || 8080; // set our port
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
var server = app.listen(port);
var io = require("socket.io")(server);
var routerProj = require("./routes/routes");
app.use(function (req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT ,DELETE');
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept,*");
next();
});
var config = {
...Config...
};
firebase.initializeApp(config);
var serviceAccount = require("./ServiceAcountKey.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://datatable-18f93.firebaseio.com"
});
app.use("/v1", routerProj, function (req) {
//Create HTTP server and listen on port 8000 for requests
});
// Print URL for accessing server
console.log("Server running at http://127.0.0.1:8080/");
io.on("connection", function (socket) {
console.log("user connected !");
});
io.on('save-message', function (socket) {
console.log("user saved message");
});
When I run server only connection event is working. I get " user connected in my console but not " user saved message"
What I expect ?
Print user saved message on server console when client run app ( DatatableComponent component )
try to modify your server side code to:
io.on('connection', function (socket) {
console.log('user connected !');
socket.on('save-message', function (socket) {
console.log('user saved message');
});
});
Register message listener on socket object instead of io which is received upon initial connection.
It seems my Node JS or Express is caching the results from MongoDB, this seems to be an advantage for some, but for me it is causing a problem. I don't want the json response to be cached. Please suggest how to stop this.
This is my Server.js if you notice I have used res.headers and app.disable methods to prevent caching.
// set up ======================================================================
var express = require('express');
var path = require('path');
var logger = require('morgan');
var bodyParser = require('body-parser');
var app = express();
var server = require('http').Server(app);
var mongoose = require('mongoose'); // mongoose for mongodb
var port = process.env.PORT || 8000; // set the port
var database = require('./config/database'); // load the database config
var morgan = require('morgan');
var methodOverride = require('method-override');
var io = require('socket.io')(server);
var cors = require('cors');
var messageId = {};
// configuration ===============================================================
// Connect to DB
mongoose.connect(database.remoteUrl)
mongoose.Promise = global.Promise;
mongoose.connection.on('error', function(e) {
console.log('Can not connect Error:>>',e);
process.exit();
});
mongoose.connection.once('open', function(d) {
console.log("Successfully connected to the database");
})
//app.use(express.static('./public')); // set the static files location /public/img will be /img for users
app.use(morgan('dev')); // log every request to the console
app.use(bodyParser.urlencoded({'extended': 'true'})); // parse application/x-www-form-urlencoded
app.use(bodyParser.json()); // parse application/json
app.use(bodyParser.json({type: 'application/vnd.api+json'})); // parse application/vnd.api+json as json
app.use(methodOverride('X-HTTP-Method-Override')); // override with the X-HTTP-Method-Override header in the request
app.use(bodyParser.urlencoded({extended:true}))
app.use(bodyParser.json())
app.use(cors());
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header('Access-Control-Allow-Methods', 'DELETE, PUT');
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
res.header("Cache-Control", "no-cache, no-store, must-revalidate");
res.header("Pragma", "no-cache");
res.header("Expires", 0);
next();
});
app.disable('view cache');
io.set('origins', '*:*');
http = require('http'),
server = http.createServer(function (req, res) {
//res.writeHead(200,{'content-type':'text/plain'});
// res.write("Sever On");
// res.end();
}),
io = io.listen(server);
io.on('connection', function (socket) {
console.log('User Connected -- Server Online');
socket.on('message', function (msg,msgId) {
io.emit('message', "Hello");
console.log("message from client:", msg);
setInterval(function(){
io.emit("messageStatus",msgId);
},500)
});
});
app.use(require('./app/routes.js'));
app.listen(port);
//server.listen(port);
console.log("App listening on port " + port);
This is my Route.js
var express = require('express')
var app = module.exports = express.Router();
var UserProfile = require('./models/UserProfile');
app.get('/User', function (req, res) {
UserProfile.find({
EmailID: req.query.EmailID
}, function (err, profile) {
// if there is an error retrieving, send the error. nothing after res.send(err) will execute
if (err) {
return res.json({
"success": false,
"msg": err
})
console.log(err);
}
res.status(200).send(profile)
});
});
This is my provider.js
import { Injectable } from '#angular/core';
import { Http } from '#angular/http';
import 'rxjs/add/operator/map';
#Injectable()
export class ProfileProvider {
data : any
constructor(public http: Http,) {
}
// public getProfile(EmailID){
// console.log(this.http.get(CONFIG.apiUrl+'User?EmailID='+EmailID).map(response => response.json().result));
// return this.http.get(CONFIG.apiUrl+'User?EmailID='+EmailID).map(response => response.json().result);
// }
public getProfile(EmailID){
console.log("Provider,>>",EmailID)
if (this.data) {
return Promise.resolve(this.data); }
return new Promise(resolve => {
this.http.get('http://192.168.0.100:8000/User?EmailID='+EmailID)
.map(res => res.json())
.subscribe(data => {
this.data = data;
resolve(this.data);
});
});
}
}
Now if run this (http://192.168.0.100:8000/User?EmailID=abc#abc.com) on my browser and if I change the email ID, i get different responses. But in the ionic app it some how gives me the same responses even after changing the parameters
And I am using AWS and my MongoDB is hosted there.
Your problem is in the provider. You are caching the results of the api call in the data property.
if (this.data) {
return Promise.resolve(this.data); }
Try always fetching the data:
public getProfile(EmailID){
return new Promise(resolve => {
this.http.get('http://192.168.0.100:8000/User?EmailID='+EmailID)
.map(res => res.json())
.subscribe(data => {
resolve(data);
});
});
}