WebSocket with angular universal - node.js

I'm trying to add websockets to a server that's using angular universal. As far as I can tell, express is consuming my request before it gets to my sockets, but I could be wrong about that.
I get this error in chrome:
WebSocket connection to 'ws://localhost:4200/socket' failed: Connection closed before receiving a handshake response
and I get this error in firefox:
Firefox can’t establish a connection to the server at ws://localhost:4200/socket.
when I run a separate nodejs server without the angular code, the websockets work fine.
Here is the relevant part of my server.ts
import 'zone.js/dist/zone-node'
import { ngExpressEngine } from '#nguniversal/express-engine'
import * as express from 'express'
import { join } from 'path'
import { AppServerModule } from './src/main.server'
import { APP_BASE_HREF } from '#angular/common'
import { existsSync } from 'fs'
import * as WebSocket from 'ws'
// The Express app is exported so that it can be used by serverless Functions.
export function app() {
const server = express()
const distFolder = join(process.cwd(), 'dist/CAHClone/browser')
const indexHtml = existsSync(join(distFolder, 'index.original.html')) ? 'index.original.html' : 'index'
// Our Universal express-engine (found # https://github.com/angular/universal/tree/master/modules/express-engine)
server.engine('html', ngExpressEngine({
bootstrap: AppServerModule,
}))
server.set('view engine', 'html')
server.set('views', distFolder)
server.use(express.json())
// Serve static files from /browser
server.get('*.*', express.static(distFolder, {
maxAge: '1y'
}))
// All regular routes use the Universal engine
server.get('*', (req, res, next) => {
if (req.url === '/socket') return next() // leave '/socket' open for the websockets
res.render(indexHtml, { req, providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }] })
})
return server
}
function run() {
const port = process.env.PORT || 4000
// Start up the Node server
const expressApp = app()
const server = expressApp.listen(port, () => {
console.log(`Node Express server listening on http://localhost:${port}`)
})
// Websockets
const wss = setupWebsockets(server)
}
function setupWebsockets(server) {
const wss = new WebSocket.Server({ server, path: '/socket' })
wss.on('connection', ws => {
console.log('Client connected.')
ws.send({message: 'Hi there!'})
})
wss.on('message', msg => {
console.log('Client said: ' + msg.toString())
})
return wss
}
declare const __non_webpack_require__: NodeRequire
const mainModule = __non_webpack_require__.main
const moduleFilename = mainModule && mainModule.filename || ''
if (moduleFilename === __filename || moduleFilename.includes('iisnode')) {
run()
}
export * from './src/main.server'
And here is a server.js file that works without angular related code
const express = require('express')
const WebSocket = require('ws')
const { join } = require('path')
const { existsSync } = require('fs')
// The Express app is exported so that it can be used by serverless Functions.
function app() {
const server = express()
server.use(express.json())
server.use('*', (req, res, next) => {
if (req.url === '/socket') return next()
res.end('Hello, world!')
})
return server
}
function setupWebsockets(server) {
const wss = new WebSocket.Server({ server, path: '/socket' })
console.log('set up websocket server');
wss.on('connection', ws => {
console.log('Client connected.');
ws.send('Hi there!')
})
wss.on('message', msg => {
console.log('Client said: ' + msg.toString());
})
}
function run() {
const port = process.env.PORT || 4000
const expressApp = app()
const server = expressApp.listen(port, () => {
console.log(`Node Express server listening on http://localhost:${port}`)
})
// Websockets
const wsServer = setupWebsockets(server)
}
run()
Could anyone help me understand what part of angular universal is breaking my websockets, and how to fix it?

I ran into this issue with the 'ws' module and passing in an express server. Try creating your server with the 'http' module and pass it into both express and 'ws'. I also recommend taking care of any authorization that you are going to do in the 'upgrade' event if necessary.

Did you solve the matter with dev:ssr ?
It is indeed working fine with serve:ssr but the only way I made it work with dev:ssr in local development is by using a proxy from ws://localhost:4200/ws to ws://localhost:4000
{
"/ws": {
"target": "ws://localhost:4000",
"secure": false,
"ws": true
}
}
Se my question here : Angular Universal (ssr-dev-server) Setting or getting the random port of the express app in dev:ssr

Related

React/Socket.io client not connecting to server when deployed to Heroku

I'm following this tutorial: https://www.youtube.com/watch?v=tBr-PybP_9c
Locally, the app works fine - I can open up some windows and have them talk to each other. However, when I try to deploy the app to Heroku, the clients no longer connect to the server.
Server-side:
const express = require('express');
const cors = require('cors');
const socket = require('socket.io');
const dotenv = require('dotenv');
dotenv.config({ path: './config.env' });
const app = express();
app.use(cors());
const port = process.env.PORT || 8080;
const server = app.listen(port, () => {
console.log(`App running on port ${port}`);
});
const http = require('http').Server(app);
const io = socket(http, {
pingInterval: 100,
pingTimeout: 500,
cors: {
origin: '*',
},
});
io.listen(server);
io.on('connection', (socket) => {
const id = socket.handshake.query.id;
socket.join(id);
console.log(`A user has connected with ID ${id}`);
});
I deployed the server to a Heroku dyne, and it built successfully. The client is a separate dyne:
import React, { useContext, useEffect, useState } from 'react';
import io from 'socket.io-client';
const SocketContext = React.createContext();
export function useSocket() {
return useContext(SocketContext);
}
export function SocketProvider({ id, children }) {
const [socket, setSocket] = useState();
useEffect(() => {
// const newSocket = io('https://my-heroku-app.herokuapp.com:48600', {
const newSocket = io('http://localhost:8080', {
query: { id },
});
setSocket(newSocket);
return () => newSocket.close();
}, [id]);
return (
<SocketContext.Provider value={socket}>{children}</SocketContext.Provider>
);
}
This also built successfully. Obviously, localhost:8080 doesn't work when deployed to Heroku - I tried using 'https://my-heroku-app.herokuapp.com:48600' (the server application's name plus the port where it's running on Heroku), and that doesn't work either. The console.log never occurs on the server side when I do heroku logs --tail.
Is there a better way to do this? Can I put these into the same app, so I only have to deploy one project?

Translating server code to ES6 using socket.io

We are complete newbies to socket.io and express. And we have followed along this tutorial to learn socket.io https://www.valentinog.com/blog/socket-react/
And now we want to translate this line of code (older style):
const index = require("./routes/index").default
to ES6, below:
import router from './routes/index'
app.use('/', router)
But it does not work for us. We get this error in the terminal.
Full server.js code here
import express from 'express'
const app = express()
import { createServer } from 'http'
const server = createServer(app)
import { Server } from "socket.io"
const io = new Server(server)
import cors from 'cors'
import router from './routes/index'
const port = process.env.PORT || 4001
app.use('/', router)
app.use(index)
app.use(cors())
app.use(express.json())
let interval
io.on("connection", (socket) => {
console.log("New client connected")
if (interval) {
clearInterval(interval)
}
interval = setInterval(() => getApiAndEmit(socket), 1000)
socket.on("disconnect", () => {
console.log("Client disconnected")
clearInterval(interval)
})
})
const getApiAndEmit = socket => {
const response = new Date()
socket.emit("FromAPI", response)
}
app.listen(port, () => {
// eslint-disable-next-line
console.log(`Server running on http://localhost:${port}`)
})
I was able to use socket.io on my project like so:
const app = express()
const http = require('http').createServer(app)
const socketIo = require('socket.io')(http)
In other words I used require and did not use router. This might work for you unless there is a specific reason you need to do otherwise.

How to get a reference to the Nextjs server

I'm trying to utilize ws (web socket) in my Nextjs app.
Instead of creating a new server, I want to pass the current server object to the ws initialization:
const { Server } = require('ws');
wss = new Server({ myNextJs server instance here ... });
So: how to get a reference to the Nextjs server at run time?
You can create a custom server. See https://nextjs.org/docs/advanced-features/custom-server
Here is an example:
const express = require("express");
const next = require("next");
const dev = process.env.NODE_ENV !== "production";
const app = next({ dev });
const handle = app.getRequestHandler();
const server = express();
app.prepare().then(() => {
server.get("/some-random-path", (req, res) => res.send("Hello, world!"));
server.get("*", (req, res) => handle(req, res));
server.listen(3000, "0.0.0.0", () => {
console.log("Application started on http://localhost:3000");
});
});
Then just run your new server file
you can merge the code from my answer about socket.io
https://stackoverflow.com/a/62547135/2068876
with the example given on Github:
https://github.com/websockets/ws#multiple-servers-sharing-a-single-https-server
try something like this (not tested, but it seems valid since the principle is the same):
./pages/api/wss1.js
import { WebSocketServer } from "ws";
const wss1Handler = (req, res) => {
if (!res.socket.server.wss1) {
console.log("*First use, starting wss1");
const wss1 = new WebSocketServer({ noServer: true });
res.socket.server.on("upgrade", function upgrade(request, socket, head) {
wss1.handleUpgrade(request, socket, head, function done(ws) {
wss1.emit('connection', ws, request);
});
});
res.socket.server.wss1 = wss1;
} else {
console.log("wss1 already running");
}
res.end();
}
export const config = {
api: {
bodyParser: false
}
}
export default wss1Handler;

Socket.io Node Js Server and React js Client not connecting

So first I would like to say that I have looked at many other answers that were given for similar questions, but none worked for me.
My setup is a node js server and a react js client. And I am having trouble doing just a basic setup. Any one who would help me out here, I really appreaciate.
And also on the client code I have alternated through different options for serverUrl from localhost with the http http://localhost:6000 and without localhost:6000. Same for ip address.
NODE JS Server Code
const express = require('express');
const app = express();
const users = require("./routes/api/users");
const profile = require("./routes/api/profile");
const project = require("./routes/api/project");
const auth = require("./routes/api/auth");
const email = require("./routes/api/email");
app.use(express.static(__dirname + '/public'));
const server = require('http').createServer(app);
const io = require('socket.io')(server);
io.on('connection', (socket)=> {
console.log("user connected")
socket.on('SEND_MESSAGE', function(data){
console.log("message received")
io.emit('RECEIVE_MESSAGE', data);
})
});
//*** Start of Routes ***//
app.use((req, res, next) => {
res.setHeader("Access-Control-Allow-Origin", "http://localhost:6000");
res.setHeader("Access-Control-Allow-Credentials", "true");
next();
})
app.use("/api/users", users);
app.use("/api/profile", profile);
app.use("/api/auth", auth);
app.use("/api/project", project);
app.use("/api/email", email);
//*** End of Routes ***//
const port = 6000;
server.listen(port, () => {
console.log(`Server Running at ${port}`);
});
REACT JS Client Code
import React,{useEffect,useState,useRef} from 'react';
import io from "socket.io-client";
class App extends React.Component {
constructor(props){
super(props);
this.state = {
username: 'name',
message: 'hello world',
messages: []
};
this.serverUrl = '127.0.0.1:6000';
this.socket = io(this.serverUrl, {reconnect: true});
this.setupSocket();
}
setupSocket() {
this.socket.on('connection', (data) => {
console.log(`Connected: ${data}`);
});
}
render(){
return(<p>Hello<p>)
}
}
export default App
It may have a problem with your socket server you can change your port and check if it is working

Angular Universal page loading/refreshing issue

I have added Angular Universal in my project(upgraded to angular 8). Which is comparatively a big project. After solving all the window, jquery and localstorage issue I have successfully able to run the app. But the problem is, the server runs on localhost:4000. when I type localhost:4000 in browser nothing happens. But if I put localhost:4000/index.html, It starting to load fine and return to base localhost:4000. Then every angular route works fine. But if I try to refresh any page it doesn't load anything.
my server.ts
import 'zone.js/dist/zone-node';
// ssr DOM
const domino = require('domino');
const fs = require('fs');
const path = require('path');
const template = fs.readFileSync(path.join('dist/browser', 'index.html')).toString();
const win = domino.createWindow(template);
global['window'] = win;
Object.defineProperty(win.document.body.style, 'transform', {
value: () => {
return {
enumerable: true,
configurable: true,
};
},
});
// mock documnet
global['document'] = win.document;
// othres mock
global['CSS'] = null;
// global['XMLHttpRequest'] = require('xmlhttprequest').XMLHttpRequest;
global['Prism'] = null;
import { ngExpressEngine } from '#nguniversal/express-engine';
import * as express from 'express';
import { join } from 'path';
import { AppServerModule } from './src/main.server';
import { APP_BASE_HREF } from '#angular/common';
import { existsSync } from 'fs';
// The Express app is exported so that it can be used by serverless Functions.
export function app() {
const server = express();
const distFolder = join(process.cwd(), 'dist/browser');
const indexHtml = existsSync(join(distFolder, 'index.original.html')) ? 'index.original.html' : 'index';
// Our Universal express-engine (found # https://github.com/angular/universal/tree/master/modules/express-engine)
server.engine('html', ngExpressEngine({
bootstrap: AppServerModule,
}));
server.set('view engine', 'html');
server.set('views', distFolder);
// Example Express Rest API endpoints
// app.get('/api/**', (req, res) => { });
// Serve static files from /browser
server.get('*.*', express.static(distFolder, {
maxAge: '1y'
}));
// All regular routes use the Universal engine
server.get('*', (req, res) => {
res.render(indexHtml, { req, providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }] });
});
return server;
}
function run() {
const port = process.env.PORT || 4000;
// Start up the Node server
const server = app();
server.listen(port, () => {
console.log(`Node Express server listening on http://localhost:${port}`);
});
}
// Webpack will replace 'require' with '__webpack_require__'
// '__non_webpack_require__' is a proxy to Node 'require'
// The below code is to ensure that the server is run only when not requiring the bundle.
declare const __non_webpack_require__: NodeRequire;
const mainModule = __non_webpack_require__.main;
if (mainModule && mainModule.filename === __filename) {
run();
}
export * from './src/main.server';
I think i need some change with this file to load correctly.

Resources