React js - problem with proxy for web socket link in react - node.js

I am setting up the proxy server using setupProxy for my react app using graphql as backend which is running on a different part, doing so The HTTP link proxy is working fine but WebSocket link proxy is giving me an error
For solving the problem I have tried to include options as ws: true, but it's not working.
The error is as follows:
SyntaxError: Failed to construct 'WebSocket': The URL '/ws' is invalid.
Error:
setupProxy.js
const proxy = require("http-proxy-middleware");
module.exports = function(app) {
app.use(proxy("/graphql", { target: "http://localhost:8001/graphql" }));
app.use(
proxy("/ws", {
target: "ws://localhost:8001",
ws: true,
})
);
};
index.js
import { WebSocketLink } from "apollo-link-ws";
import { createUploadLink } from "apollo-upload-client";
//Apollo Imports End
// Declaring constants for GraphQL
const httpLink = new createUploadLink({
uri: "/graphql"
});
const wsLink = new WebSocketLink({
uri: "/ws",
options: {
reconnect: true
}
});
I expected the call should be same as normal call but its throwing an error.

/ws is not a valid URI for a WebSocket class.
A websocket expect a full URL to connect, you can try it in your browser console:
In this case, Apollo is using the native web socket class behind the scenes, so if you can make this work, it will work in Apollo too.
Try using ws://localhost:8001 instead.

Just replace with this
const proxy = require("http-proxy-middleware");
module.exports = function(app) {
app.use(proxy("/graphql", { target: "http://localhost:8001/graphql" }));
app.use(proxy("ws://locahost:8001"));
};
OR
const proxy = require("http-proxy-middleware");
module.exports = function(app) {
app.use(proxy("/graphql", { target: "http://localhost:8001/graphql" }));
app.use(proxy('/ws', {
target: 'http://localhost:8001',
ws: true
})
);
};

Adding this in setupProxy.js file worked for me :
const { createProxyMiddleware } = require("http-proxy-middleware");
module.exports = app => {
const wsProxy = createProxyMiddleware("/graphql", {
ws: true,
changeOrigin: true,
autoRewrite: true,
target: "http://localhost:8001",
});
app.use(wsProxy);
};
This is what I have in my apollo-client.ts file
const httpLink = new HttpLink({
uri: "/graphql",
credentials: "include",
});
const wsLink = new WebSocketLink({
uri: `ws://${window.location.host}/graphql`,
options: {
reconnect: true,
lazy: true,
connectionParams: {
headers: {
//add your headers here
},
},
},
});
const link = split(
// split based on operation type
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === "OperationDefinition" &&
definition.operation === "subscription"
);
},
wsLink,
httpLink,
);

Related

Difficulty setting Cookie when using an ngrok tunnel (Express server on Node.js, React app frontend)

As outlined in the title, I am having difficulty setting a http cookie to be used for auth purposes when tunnelling using ngrok.
The following code works fine (obviously with the relevant endpoints specified) when i am running a query from from localhost to a localhost endpoint in my dev environment but breaks down as soon as i start to query the ngrok tunnel endpoint.
Frontend api query (simplified as part of larger application)
function fetchRequest (path, options) {
const endpoint = 'http://xxx.ngrok.io'; // the ngrok tunnel endpoint
return fetch(endpoint + path, options)
.then(res => {
return res.json();
})
.catch((err) => {
console.log('Error:', err);
});
}
function postRequest (url, body, credentials='include') {
return fetchRequest(`${url}`, {
method: 'POST',
withCredentials: true,
credentials: credentials,
headers: {'Content-Type': 'application/json', Accept: 'application.json'},
body: JSON.stringify(body)
});
}
// data to be passed to backend for authentication
let data = {pin: pin, username : username};
postRequest('/',data)
Express server on Node.js with ngrok tunnel (app.js)
const express = require('express')
const session = require('express-session')
const cors = require('cors')
const router = require('./router');
const tunnel = require('./ngrok')
const app = express()
const port = process.env.PORT || 4001;
app.use(cors({
origin: 'http://localhost:3000'
credentials: true,
}))
app.use(express.json());
const expiryDate = new Date(Date.now() + 60 * 60 * 1000) // 1 hour
app.use(session({
secret: 'keyboard cat',
resave: false,
saveUninitialized: true,
cookie: {
httpOnly: true,
expires: expiryDate
// sameSite: 'none'
// secure: true
}
}))
app.use(router)
let useNGROK = true;
if (useNGROK) {
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})
tunnel.createHTTPtunnel().then((url) => {
console.log(`New tunnel created with endpoint: ${url}`)
});
} else {
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})
}
Ngrok configuration (ngrok.js)
const ngrok = require('ngrok');
const find = require('find-process');
const port = process.env.PORT || '3000';
const tunnel = {
createHTTPtunnel: async function () {
const list = await find('name', 'ngrok');
if (list.length > 0) {
let api = ngrok.getApi();
if (api == null) {
this.kill_existing_tunnel();
} else {
let open_tunnels = await ngrok.getApi().listTunnels();
return open_tunnels.tunnels[0].public_url;
}
}
let ngrok_config = {
proto: 'http',
bind_tls: false,
name: process.env.NGROK_NAME,
hostname: process.env.NGROK_CUSTOM_DOMAIN,
// host_header: 'rewrite',
authtoken: '',
region: 'eu',
};
return ngrok.connect({ ...ngrok_config, addr: port });
},
kill_existing_tunnel: async () => {
const list = await find('name', 'ngrok');
list.forEach((p) => {
try {
process.kill(p.pid);
console.log(`Killed process: ${p.name} before creating ngrok tunnel`);
} catch (e) {
console.log(e);
}
});
}
}
module.exports = tunnel;
** router & controller (router.js & controller.js respectively) **
*router.js*
const router = require('express').Router();
const example = require('./controller')
router.post('/', example.authenticate);
module.exports = router;
*controller.js*
async function authenticate (req, res) {
try {
res.send(JSON.stringify('trying to send cookie'))
} catch (e) {
console.log('Error', e)
res.sendStatus(500)
}
}
module.exports = {
authenticate
};
The following information is provided when inspecting the Set-Cookie response header in the network requests:
This Set-Cookie header didn’t specify a “SameSite” attribute and was defaulted to “SameSite=Lax” and was blocked because it came from a cross-site response which was not the response to a top-level navigation. The Set-Cookie had to have been set with “SameSite=None” to enable cross site usage.
Attempted fix 1//
If I add the following options to the cookie {sameSite: ‘none’, secure:true}, amend the ngrok config to set {bind_tls: true} and run https on my front end (using a custom SSL certificate as per the create react app documentation), and query the https tunnel, then no cookie is received in the response from the server at all (request is sent and response 200 is received but with no cookie).
Attempted fix 2//
I also tried to change the host_header option to rewrite in the ngrok config (to mirror a response from localhost rather than from ngrok) and this did not work.
Any help would be much appreciated as I have little experience and I am stuck!

node js express server postgres 500 internal server error

My api has stopped working, previously it worked fine and as far as i am aware I have changed nothing. When i tested my endpoint i received an internal server error.
Here is a link to my hosted api https://frozen-scrubland-34339.herokuapp.com/api
I have just checked some of my other apis and none are working either, same message. it appears my code isnt the issue but postgres itself?
Any help on what to do would be appreciated
When i tried to npm run prod to re-push it to heroku i received: 'Error: The server does not support SSL connections'
Again this was never an issue previously when it worked.
I imagine i have changed something with heroku itself by accident?
app.js
const express = require("express");
const app = express();
const apiRouter = require("./routers/api-router");
const cors = require("cors");
const {
handle404s,
handlePSQLErrors,
handleCustomError,
} = require("./controllers/errorHandling");
app.use(cors());
app.use(express.json());
app.use("/api", apiRouter);
app.use("*", handle404s);
app.use(handlePSQLErrors);
app.use(handleCustomError);
module.exports = app;
connection.js
const { DB_URL } = process.env;
const ENV = process.env.NODE_ENV || "development";
const baseConfig = {
client: "pg",
migrations: {
directory: "./db/migrations",
},
seeds: {
directory: "./db/seeds",
},
};
const customConfigs = {
development: { connection: { database: "away_days" } },
test: { connection: { database: "away_days_test" } },
production: {
connection: {
connectionString: DB_URL,
ssl: {
rejectUnauthorized: false,
},
},
},
};
module.exports = { ...baseConfig, ...customConfigs[ENV] };

CORS error with socket-io connections on Chrome v88+ and Nestjs server

I have an application connecting to a Nestjs server to establish a WS connection (server is on a different URL, so it is a CORS request).
The WebsocketGateway is defined as such.
#WebSocketGateway(port, {
handlePreflightRequest: (req, res) => {
const headers = {
'Access-Control-Allow-Headers': 'Authorization',
'Access-Control-Allow-Origin': 'the page origin',
'Access-Control-Allow-Credentials': true,
};
res.writeHead(200, headers);
res.end();
}
})
Works like a charm on Chrome v87 and down and on Firefox. Since upgrading my browser to Chrome 88, the front-end socket-io connection goes on a connect-reconnect loop, as:
The preflight request passes and gets a 200 response, with the headers set above;
The actual connection fails with CORS error as the only message in the browser console
Just incase someone else needs this, in your decorator there is a cors property
#WebSocketGateway({ cors: true })
This is how i fixed
import { IoAdapter } from '#nestjs/platform-socket.io';
import { ServerOptions } from 'socket.io';
export class SocketAdapter extends IoAdapter {
createIOServer(
port: number,
options?: ServerOptions & {
namespace?: string;
server?: any;
},
) {
const server = super.createIOServer(port, { ...options, cors: true });
return server;
}
}
main.ts
const app = await NestFactory.create(AppModule, { cors: true });
app.useWebSocketAdapter(new SocketAdapter(app));

can't connect to Socket.IO server on Heroku

I want to host my frontend on Vercel (I'm using Nextjs) and since it doesn't support socket connections in it's API routes I decided to move this part of my app to Heroku. My problem is that when I use the server from my frontend in dev environment it works just fine, but when I deploy it to Heroku I get this error:
Access to XMLHttpRequest at 'https://my-socket-server.herokuapp.com/socket.io/?EIO=3&transport=polling&t=NMPkkyL' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
my server code looks like this:
import express from 'express';
import io, { Namespace } from 'socket.io';
import { PORT, menus, socketServerConfig, adminRoom } from './util/config';
const server = express().listen(PORT, () =>
console.log(`server running on port ${PORT}`)
);
const socketServer = io(server, socketServerConfig);
const attachSocketHandlers = (server: Namespace) => {
server.on('connection', socket => {
const handlers = {
'admin-log-in': () => {
socket.join(adminRoom);
},
'need-waiter': (table: Table) => {
server.to(adminRoom).emit('need-waiter', table);
},
'need-receipt': (table: Table) => {
server.to(adminRoom).emit('need-receipt', table);
},
order: (order: Order) => {
server.to(adminRoom).emit('order', order);
},
disconnect: () => {
socket.leaveAll();
},
};
Object.entries(handlers).map(([event, handler]) => {
socket.on(event, handler);
});
});
};
menus.forEach(menu => {
const namespacedServer = socketServer.of(`/${menu}`);
attachSocketHandlers(namespacedServer);
});
What I understood from the socket.io docs is that if you list no origins in the config it allows all origins to access the socket server.This is my socketServerConfig:
import { ServerOptions } from 'socket.io';
const defaultPort = 4000;
export const PORT = process.env?.PORT ?? defaultPort;
export const menus = ['more'];
export const adminRoom = 'admin';
export const socketServerConfig: ServerOptions = {
serveClient: false, // i don't serve any static files
};
this is how I connect from my frontend:
const url = 'https://my-socket-server.herokuapp.com/';
const io = connect(`${url}${menu}`);
I tried various solutions from SOF but I just can't get it to work, any help would be appreciated. Thanks in advance.
Try doing npm install cors and adding this to your server:
const cors = require('cors');
const whitelist = [
'http://localhost:3000',
YOUR FRONTEND URL
];
const corsOptions = {
origin: function (origin, callback) {
console.log('** Origin of request ' + origin);
if (whitelist.indexOf(origin) !== -1 || !origin) {
console.log('Origin acceptable');
callback(null, true);
} else {
console.log('Origin rejected');
callback(new Error('Not allowed by CORS'));
}
},
};
express().use(cors(corsOptions));
Hope this helped!

How to set up http-proxy-middleware on a dynamic route?

I'm trying to proxy some asset routes where a dynamic part of the path comes from a config file. I had this working using the request library, but I can't quite get it working with http-proxy-middleware.
Here's the code that works when I use the request library:
const assets = express.Router();
app.use(`/${p.name}/assets`, assets);
assets.get('*', async (req, res) => {
return request(`${p.address}/${p.version}/assets${req.path}`).pipe(res);
});
I've tried a few different variations with http-proxy-middleware, and none of these work. Example 1:
app.use(
`/${p.name}/assets`,
httpProxy({
target: `${p.address}`,
changeOrigin: true,
pathRewrite: {
[`^${p.name}`]: `${p.version}`,
},
})
);
Example 2:
app.use(
`/${p.name}/assets`,
httpProxy({
target: `${p.address}`,
changeOrigin: true,
pathRewrite: function(path) {
return path.replace(p.name, p.version);
}
})
);
I've also tried using /${p.name}/assets/** as the first argument to app.use, and I've also tried adding a /assets to the end of both the key and value in the pathRewrite object. The result is always the same: I get a 302 for the asset requested by the browser.
I've even tried adding a middleware function right before the httpProxy call that logs to the console so that I know my request is hitting the correct route:
app.use(
`/${p.name}/assets`,
(req, _res, next) => {
console.log("I'm an asset proxy, short and stout: ", req.url);
next();
},
httpProxy({
target: `${p.address}`,
changeOrigin: true,
pathRewrite: {
[`^${p.name}`]: `${p.version}`,
},
})
);
But I don't ever see that output. Probably a simple mistake. Perhaps my first argument to app.use just isn't right? Help appreciated!
Update
I also tried a combination of the old way and the new. I mounted a new router on /${p.name}/assets, and after discovering the onProxyRequest option, I added a function there to log some output.
const assets = express.Router();
app.use(`/${p.name}/assets`, assets);
assets.use(
'*',
httpProxy({
target: p.address,
changeOrigin: true,
pathRewrite: {
[`^${p.name}`]: p.version,
},
onProxyReq: (proxyReq, req, res) => {
console.log("Hello I'm being proxied now! ", proxyReq, req, res);
},
})
);
Still getting a 302, and I never see the output from the onProxyReq function.
Documentation has custom router option.
-
const proxyTable = {
'integration.localhost:3000': 'http://localhost:8001', // host only
'staging.localhost:3000': 'http://localhost:8002', // host only
'localhost:3000/api': 'http://localhost:8003', // host + path
'/rest': 'http://localhost:8004', // path only
};
const options = {
target: 'http://localhost:8000',
router: proxyTable,
};
const myProxy = createProxyMiddleware(options);
or
function customRouter(req) {
// I dont know what "p" stands for
// check the req object to see which property you want to modify
console.log(req)
const { hostname } = req
const hostName = hostname.split('.')[0]
return `https://${hostName}.sub.domain.com`
}
then add this option:
router: customRouter
Can you check your installed version - looks like you are using the v0.x.x.
interface to the http-proxy-middleware package. Latest is version 2.X.X and it exposes the following function
const { createProxyMiddleware } = require('http-proxy-middleware');
app.use(
`/${p.name}/assets`,
createProxyMiddleware({
target: `${p.address}`,
changeOrigin: true,
pathRewrite: {
[`^${p.name}`]: `${p.version}`,
},
})
);

Resources